一,概述
ThreadLocal,其作用是实现线程独享数据,用于隔离每个线程对于同一个值的访问,核心方法只有三个:
ThreadLocal#set 设置线程独享数据
ThreadLocal#get 获得线程独享数据
ThreadLocal#remove清除线程独享数据
本文主要谈谈ThreadLocal基本使用、实现原理、以及使用注意事项的讨论。
二,实例
public class ThreadLocalMain {
//定义一个ThreadLocal
public static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>() {
@Override
protected Integer initialValue() {
//重写initialValue方法,否则默认返回null作为初始值
System.out.println("init value for thread:" + Thread.currentThread().getName());
return Thread.currentThread().hashCode();
}
};
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
//线程中get
System.out.println(threadLocal.get());
//线程中set
threadLocal.set(1);
//线程中remove
threadLocal.remove();
}).start();
}
}
}
三,原理
ThreadLocal既然线程数据独享,则必定有一个线程->数据映射的map,这个map要么放置在Thread、要么放置在ThreadLocal。显然放在Thread合理,因为ThreadLocal实例存在多个,ThreadLocal自身可以作为一个key,绑定到每个Thread的map中,所需创建的map较少,且不改变Thread的引用。如果将map放置到ThreadLocal,显然ThreadLocal会引用到Thread,不利于Thread的gc。
关于在于怎么设计,ThreadLocal本身即为一个key。
在java实现中,这个map放置在Thread
Thread.map = ThreadLocal.ThreadLocalMap threadLocals;
这是一个数组实现的map,具体实现此处不赘述。
1,get
get只暴露如下方法
以上逻辑分三步走,
1,拿到线程map
2,将ThreadLocal实例作为key,从map中取值并返回
3,如果未set值,则调用setInitialValue方法初始化
getMap方法实现如下,很简单,直接取thread#threadLocals
对于map为null或未设置值的情况,会走setInitialValue,跟进看下逻辑
如果map存在直接set即可,否则通过creatMap创建map,
创建方式很简单,传入初始key和value即可,
这样,Thread上下文中就可通过Thread#threadLocal获得ThreadLocal作为key的value了。
2,set
set方法与setInitialValue方法逻辑类似,就不赘述了
核心在于set中有一个关于引用的特殊处理,能规避一些内存泄漏,咱们看下
2 是对ThreadLocal是否gc的判断,如果已经gc,refersTo返回false。
3 如果ThreadLocal已经gc,则重新新建一个引用关系,赋值。
Entry声明如下
可以看到,ThreadLocal作为key是以弱引用方式,传入到Entry中,即Map对应散列表Node。
但set的值,仍是强引用保存到Thread#threadLocals#Entry[]中。以上只对ThreadLocal本身做了内存泄漏防护,对保存的value如果不及时remove,是会一直存在于Thread中,直至Thread销毁。
3,remove
remove比较简单,不赘述