https://mp.weixin.qq.com/s/SEzGM5wPyL066Y3fWAeWjA
1.为什么使用ThreadLocal。
为了解决线程安全问题,JDK出现了很多技术手段,比如:使用synchronized或Lock,给访问公共资源的代码上锁,保证了代码的原子性。
但在高并发的场景中,如果多个线程同时竞争一把锁,这时会存在大量的锁等待,可能会浪费很多时间,让系统的响应时间一下子变慢。
因此,JDK提供了另外一种用空间换时间的新思路:ThreadLocal。核心思想是:共享变量在每个线程都有一个副本,每个线程操作的都是自己的副本,对另外的线程没有影响。
@Service
public class ThreadLocalService {
private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public void add() {
//将数据设置到当前线程中
threadLocal.set(1);
doSamething();
//从当前线程中获取数据
Integer integer = threadLocal.get();
}
}
2.ThreadLocal原理
ThreadLocal的内部有一个静态的内部类叫:ThreadLocalMap。
public class ThreadLocal<T> {
...
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的成员变量ThreadLocalMap对象
//return t.threadLocals; ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap map = getMap(t);
if (map != null) {
//根据threadLocal对象从map中获取Entry对象
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();
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的成员变量ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//如果map不为空
if (map != null)
//将初始值设置到map中,key是this,即threadLocal对象,value是初始值
map.set(this, value);
else
//如果map为空,则需要创建新的map对象
createMap(t, value);
return value;
}
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的成员变量ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//如果map不为空
if (map != null)
//将值设置到map中,key是this,即threadLocal对象,value是传入的value值
map.set(this, value);
else
//如果map为空,则需要创建新的map对象
createMap(t, value);
}
static class ThreadLocalMap {
...
}
...
}
ThreadLocal的get方法、set方法和setInitialValue方法,其实最终操作的都是ThreadLocalMap类中的数据。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
//k为弱引用
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
private Entry[] table;
...
}
ThreadLocalMap里面包含一个静态的内部类Entry,该类继承于WeakReference类,说明Entry是一个弱引用。
ThreadLocalMap内部还包含了一个Entry数组,其中:Entry = ThreadLocal+value。
ThreadLocalMap被定义成了Thread类的成员变量
在每个Thread类中,都有一个ThreadLocalMap的成员变量,该变量包含了一个Entry数组,该数组真正保存了ThreadLocal类set的数据。
Entry是由threadLocal和value组成,其中threadLocal对象是弱引用,在GC的时候,会被自动回收。而value就是ThreadLocal类set的数据。
除了Entry的key对ThreadLocal对象是弱引用,其他的引用都是强引用。
需要特别说明的是,上图中ThreadLocal对象画到了堆上,在实际的业务场景中不一定在堆上。如果ThreadLocal被定义成了static的,ThreadLocal的对象是类共用的,可能出现在方法区。
3.为什么使用ThreadLocal作为key。
在应用中,如果一个线程只使用一个ThreadLocal对象,那么使用Thread做key也未尝不可。
实际情况中,一个线程中很可能使用多个ThreadLocal对象。如果使用Thread做key就有问题。因此,不能使用Thread做key,而应该改成用ThreadLocal对象做key,这样才能通过具体ThreadLocal对象的get方法,轻松获取到想要的ThreadLocal对象。
4.Entry的key为什么是弱引用。
Entry的key,传入的是ThreadLocal对象,使用了WeakReference对象,即被设计成了弱引用。
假设设计为强引用
由上图可以知,ThreadLocal对象有两个强引用(ThreadLocal变量、key)。导致后果,即使ThreadLocal变量设置为null,key仍然对ThreadLocal对象保持引用。ThreadLocal无法被释放。
强引用链:Thread变量 -> Thread对象 -> ThreadLocalMap -> Entry -> key -> ThreadLocal对象。
ThreadLocal对象和ThreadLocalMap都将不会被GC回收,于是产生了内存泄露问题。
为解决,ThreadLocal变量生命周期结束后(即ThreadLocal变量赋值为null),ThreadLocal对象可以被回收,采用弱引用。
key是弱引用,当ThreadLocal变量指向null之后,在GC做垃圾清理的时候,key会被自动回收,其值也被设置成null。
需要特别注意的地方是
- key为null的条件是,ThreadLocal变量指向null,并且key是弱引用。如果ThreadLocal变量没有断开对ThreadLocal的强引用,即ThreadLocal变量没有指向null,GC不会把弱引用的key回收。
如果当前ThreadLocal变量指向null了,并且key也为null了,但如果没有其他ThreadLocal变量触发get、set或remove方法,也会造成内存泄露。 ```java public static void main(String[] args) { Object object = new Object(); WeakReference