文章目录
一、概述
ThreadLocal是Java语言提供的一种支持线程隔离的类,用它来定义变量可以隔绝其他线程对同变量的修改。
既然是隔离线程影响的,用局部变量不可以吗?
答案是可以解决的,但是当我们的程序方法调用很深的时候,我们不断的进行方法的传递就会显得很痛苦,并且参数过长不符合各大公司的编程规范。如果改成静态变量又会线程间共享,因此我们用到了ThreadLocal。
二、ThreadLocal使用
static ThreadLocal<String> tl = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
tl.set("hello");
System.out.println("第0个线程写入成功");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一个线程读取到的内容:" + tl.get());
tl.set("world");
System.out.println("第一个线程自己写入后读取的内容:" + tl.get());
}
}).start();
}
输出结果如下:
第0个线程写入成功
第一个线程读取到的内容:null
第一个线程自己写入后读取的内容:world
我们发现在第0个线程写入数据后,第一个线程读取不到第0个线程写入的hello。而第一个线程写入world后却可以读取到自己线程写入的数据。
三、线程隔离的原理
为了找到是怎么实现的线程隔离,我们首先找到了set方法。
public void set(T value) {
Thread t = Thread.currentThread();
//获取当前线程的ThreadMap
ThreadLocalMap map = getMap(t);
if (map != null)
//将ThreadLocal对象作为key,保存的对象作为value
map.set(this, value);
else
createMap(t, value);
}
//threadLocals是Thread类中的一个变量
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
通过上边的代码我们发现,每个线程都会维护一个ThreadLocalMap的一个集合。我们存储数据时,是存储在当前线程维护的map集合中的value上的,key为ThreadLocal对象。
看完了set方法。我们猜测get方法应该是在自己线程上通过map取值。为了验证猜测,看下jdk8中源码。
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();
}
忽略ThreadLocalMap的实现,我们看到get的时候,也是取到当前线程下的map,然后调用底层数据蹭够Entry通过this(也就是ThreadLocal)取出对应的值。看到这里应该就明白ThreadLocal实现线程封闭的原理了吧。
四、ThreadLocal常见面试问题
1、ThreadLocal中Entry是弱引用,为什么
关于java中的引用,可以看下我之前的博文java中的引用
问我们为什么这里用到弱引用,那么我们假设这里是强引用会怎样,强引用在根可达情况下不会回收内存,当我们的threadLocalRef的引用置空的时候,我们线程中依旧有强引用,就会导致ThreadLocal实例在堆上无法释放,造成内存泄露。而我们用弱引用的话,虽然key这里是弱引用,但是threadLocalRef是强引用,依旧不会被回收掉。
2、ThreadLocal中内存泄露
我们在ThreadLocal中造成内存泄露的主要就是ThreadLocal对象以及我们存储的实际数据,即Entry的key和value指向的对象。key我们通过弱引用的方式。但是当ThreadLocal被回收的时候,我们实际的对象由于有value的指向不会被回收,但是我们在ThreadLocal的get和set方法中都会去删除key为null的键,从而回收实际的对象。
还存在一个问题,当我们把ThreadRef置为null的时候,如果长期不进行set或者get操作,同样会造成实际保存的对象无法被回收,我们可以主动执行remove方法进行回收value指向的实际对象。
3、ThreadLocal运用在哪些地方
spring框架的事务管理