基于 JDK 1.8
概述
ThreadLocal,顾名思义线程本地变量,即每个线程的内部,都有存有这个变量的副本,多个线程之间不可见,从而避免了对共享变量的操作,而造成的并发问题。
本文先对 ThreadLocal 进行全局的了解,ThreadLocalMap 的源码放到后面的文章中进行分析学习。
- 使用
通过 set 方法赋值,或者初始化的时候重写 initialValue 方法
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 1;
}
};
ThreadLocal 源码分析
- 每个 Thread 对象里面包含一个 ThreadLocal.ThreadLocalMap 属性,所以不同线程的这个 ThreadLocalMap 属性互不可见
- 每个线程的 ThreadLocalMap 互相不可见,因为这是线程自己的属性
- ThreadLocalMap 里面包含多个 Entry,Entry 的 key 通过弱引用指向 ThreadLocal 对象,value 为初始化设定的值
成员变量
threadLocalHashCode 用于表示 ThreadLocal 对象对应的 hash 值,每创建一个 ThreadLocal 对象,该对象的 threadLocalHashCode 值就会累加 HASH_INCREMENT 。
0x61c88647对应十进制=1640531527,(根号5-1)*2的31次方,转换成long类型就是2654435769,转换成int类型就是-1640531527,可见 HASH_INCREMENT 与黄金分割比例相关,这样的实现方式能够形成较好的散列效果。
文档中解释到这种计算散列值的方式很特殊,仅在 ThreadLocalMaps 中使用效果比较好。
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
成员方法 set
每个 Thread 都有自己的 ThreadLocalMap,Map 的 key 就是当前线程的 ThreadLocal,value 就是你要定义的 ThreadLocal 的值
public void set(T value) {
Thread t = Thread.currentThread(); // 获取当前线程对象
ThreadLocalMap map = getMap(t); // 获取该线程的 ThreadLocalMap
if (map != null) // 存在直接赋值
map.set(this, value);
else // 不存在创建一个 ThreadLocalMap,key 为当前线程,value 为设置的值
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
成员方法 get
首先获取当前线程的 ThreadLocalMap,根据 key 找到 Map 中的 Entry ,返回 entry 的 value;若 entry 不存在,返回 setInitialValue 设定的初始值
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue(); // 返回 null,或者返回重写了 initialValue 的方法
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); // 设置 key = threadlocal value = null
else
createMap(t, value); // 初始化 map
return value;
}
protected T initialValue() {
return null;
}
remove
从 Map 中移除这个 entry
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
其它成员方法
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
// 仅在继承于 ThreadLocal 的子类可见
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
ThreadLocal 与线程池
ThreadLocal 变量与线程池使用,线程池存在线程复用的情况,如果前一个线程 set 了 ThreadLocal 变量,后面重复使用的时候可能会取到前面的值。
故不建议两者组合使用,非要使用,建议先调用 remove 方法
public static void main(String[] args) throws InterruptedException {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
ExecutorService executorService = new ThreadPoolExecutor(1, 1, 5000, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100), new AbortPolicy());
executorService.execute( ()->{
System.out.println(Thread.currentThread().getName() + " set");
threadLocal.set("5");
});
Thread.sleep(5000);
executorService.execute( () ->{
System.out.println(Thread.currentThread().getName() + " get");
System.out.println(threadLocal.get());
});
}
输出:后面执行的方法获取到前面方法设定的值
pool-1-thread-1 set
pool-1-thread-1 get
5
为什么 ThreadLocalMap 使用弱引用存储 ThreadLocal
图中的连线 5 就是弱引用
若 ThreadLocalMap 对堆中的 ThreadLocal 对象是强引用,那么当栈中的 ThreadLocal ref 被清除的时候,此时堆中的 ThreadLocal 对象由于还被 Map 强引用,导致无法被回收。
若使用弱引用,那么栈中的 threadlocal ref 被清除时,ThreadLocal 对象只被 Map 弱引用所关联。根据垃圾回收规则,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象,此时内存可以被释放。
当上面的 ThreadLocal 对象被清除之后,此时 Entry 数组就会存在 key = null,value != null 的节点,为了进一步回收这部分内存,Map 的 set,get,remove 方法的执行,会去删除 key = null 的 Entry 节点,所以 entry 节点最终也会被回收(可能不会那么及时)。
所以为了减少 ThreadLocal 造成的内存泄漏问题,建议:
分析ThreadLocal的弱引用与内存泄漏问题-Java8
为什么 ThreadLocal 建议声明为 static
好处:避免重复创建TSO(Thread Specific Object,即 ThreadLocal 所关联的对象)所导致的内存浪费。