ThreadLocal详解
一、简介
ThreadLocal 是JDK提供的一个操作线程本地变量的工具,填充的数据隶属于当前操作线程栈,变量数据相对于其他线程是不可见的,起到数据隔离的作用,规避线程安全问题。
一个简单的代码示例如下:
public class ThreadLocalTest {
public static void main(String[] args) throws Exception{
ThreadLocal threadLocal = new ThreadLocal();
Thread.currentThread().setName("main-test");
threadLocal.set("123");
System.out.println("one:" + threadLocal.get());
}
}
二、原理
1、结构
Thread 中定义两个类型均为 ThreadLocalMap 的变量 threadLocals(独占) 和 inherittableThreadLocals(可共享),主要用于存储线程本地变量。ThreadLocalMap 是 ThreadLocal 的静态内部类,类似于Map的 KV 存储结构。ThreadLocal 可以理解为操作线程本地变量的工具,通过 set 方法将 value 添加到 threadLocals 中去,通过 get 方法可以去获取当前线程变量中的 threadLocals 值。如果需要移除值,则调用 remove 方法。
2、源码
2.1、set
public void set(T value) { Thread t = Thread.currentThread(); //获取当前线程对象 ThreadLocalMap map = getMap(t); //获取当前线程的threadLocals if (map != null) map.set(this, value); //如果 threadLocals 非空,添加 threadLocals 值,key为当前 ThreadLocal实例对象,value为传入的待设值 else createMap(t, value); //如果 threadLocals 为空, 则进行创建初始化操作 }
ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); //创建初始化,key为当前 ThreadLocal 实例对象 }
2.2、get
public T get() { Thread t = Thread.currentThread(); //获取当前线程对象 ThreadLocalMap map = getMap(t); //获取threadLocals if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); // threadLocals非空,以当前 ThreadLocal的实例对象为key在查找map中 if (e != null) { //有值获取返回 @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); //没有获取到值,则进行初始化操作 } private T setInitialValue() { T value = initialValue(); //初始 value Thread t = Thread.currentThread(); //当前操作线程 ThreadLocalMap map = getMap(t); //获取threadLocals if (map != null) map.set(this, value); //不为null,直接设值 else createMap(t, value); //为null,创建初始化 return value; //返回初始化创建的 value,即为null } protected T initialValue() { return null; }
2.3、remove
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); //根据当前线程对象,获取 threadLocals if (m != null) m.remove(this); //不为空,以当前 ThreadLocal 实例对象为 key 去删除 threadLocals 中相应的值 }
2.4、ThreadLocal.ThreadLocalMap
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } }
(1)ThreadLocalMap 类似于Map的 KV存储,只有基本的数组结构,并没有链表或者红黑树去解决hash冲突问题,发生冲突的时候,如果同一个 ThreadLocal,则进行覆盖作业。如果不同的话,则顺次的往后查找可存储的空间。
(2)key设计为弱引用结构,在 ThreadLocal 没有外部强引用的时候,只要发生GC回收,则会被资源回收掉,避免一定的内存泄漏。Java的对象引用可查看 https://www.cnblogs.com/eric-fang/p/10310399.html
3、问题
ThreadLocal 在保存的时候将自己当做 key 存在 ThreadLocalMap 中,并被设计成WeakReference弱引用,当 ThreadLocal 没有外部的强引用的时候,会被GC资源回收,这时候创建线程依旧存活运行的话,Entry 中 value就一直不可回收,产生内存泄漏问题。比如在使用线程池的时候,因为线程的回收再利用,之前设置的 value 就可能一直得不到释放回收,一直占据内存资源。
下面上实验对象:
(1)断点
(2) 解决
线程使用完毕,清理 threadLocals,调用ThreadLocal.remove()
4、共享 inheritTableThreadLocals
上面说明的是线程独占的threadLocals,使用看起来简单明了。JDK也提供了父子线程共享的 inheritTableThreadLocals,用于在线程间共享资源。
4.1、示例
public class ThreadLocalTest3 { public static void main(String[] args) { Thread.currentThread().setName("main-test"); ThreadLocal threadLocal = new InheritableThreadLocal(); threadLocal.set("123"); new Thread(() -> { System.out.println(threadLocal.get()); }).start(); } }
4.2、原理
截取 Thread 初始化最关键部分的代码,通过new Thread(Runnable target) 创建对象的时候,init 最终会执行到上图中的代码中位置,标志位 inheritThreadLocals 为 true,parent 为父线程,当父线程的inheritableThreadLocals变量非空的时候,将父线程的 inheritableThreadLocals 塞给当前线程的inheritableThreadLocals变量,完成线程间的数据传递。
5、应用
5.1、RequestContextHolder
public abstract class RequestContextHolder { private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal<RequestAttributes>("Request attributes"); private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal<RequestAttributes>("Request context"); public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) { if (attributes == null) { resetRequestAttributes(); } else { if (inheritable) { inheritableRequestAttributesHolder.set(attributes); requestAttributesHolder.remove(); } else { requestAttributesHolder.set(attributes); inheritableRequestAttributesHolder.remove(); } } } public static RequestAttributes getRequestAttributes() { RequestAttributes attributes = requestAttributesHolder.get(); if (attributes == null) { attributes = inheritableRequestAttributesHolder.get(); } return attributes; } }
Spring 的 RequestContextHolder 类中,使用到 ThreadLocal 保证 request 中的 value 的数据线程安全。
5.2、DateTimeContextHolder
5.3、TransactionSynchronizationManager 事务管理
6、一道面试题结束
package com.cfang.threadlocal; import java.util.concurrent.CountDownLatch; /** * @author cfang 2020/9/16 17:52 * @description */ public class ThreadLocalTest5 { private static ThreadLocal<Long> threadLocal = new ThreadLocal(); public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(1); new Thread(() -> { set(); System.out.println(get()); latch.countDown(); }).start(); latch.await(); System.out.println(get()); } public static void set() { threadLocal.set(1L); } public static long get() { return threadLocal.get(); } }
输出结果为:
为什么不是 1 null 呢? 注意看 ThreadLocal 泛型为封装类 Long,get()方法返回基本数据类型 long,触发自动拆箱 null.longValue(),完美的空指针异常了。