Java并发07之ThreadLocal
文章目录
- 1 ThreadLocal原理
- 2 内部结构
- 3 内存泄露问题
- 4 entry的key为什么被设计为弱引用
1 ThreadLocal原理
ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程上下文。
ThreadLocal和synchronized对比:
- synchronized采用以时间换空间的方式, 只提供了一份变量,让不同的线程排队访问。ThreadLocal采用以空间换时间的方式, 为每一个线程都提供了一份变量的副本,从而实现同时访问而相不干扰。
- synchronized侧重于多个线程之间访问共享资源的同步。ThreadLocal侧重于多线程中让每个线程之间的数据相互隔离。
2 内部结构
JDK8中ThreadLocal的设计是:每个线程Thread维护一个ThreadLocalMap变量,这个Map的key是ThreadLocal实例本身,value才是真正要存储的值Object。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 通过当前线程去获取当前线程维护的ThreadLocalMap(Thread类的成员变量)
if (map != null)
map.set(this, value); // this是threadLocal对象
else
createMap(t, value);
}
优点:这样设计之后每个Map存储的Entry数量就会变少。因为之前的存储数量由Thread的数量决定,现在是由ThreadLocal变量的数量决定。在实际运用当中,往往ThreadLocal变量的数量要远少于线程的数量。并且,当线程销毁之后,对应的ThreadLocalMap也会随之销毁,能减少内存的使用。
比如我们在一个线程中声明了两个ThreadLocal对象,Thread内部都是使用一个ThreadLocalMap存放数据的,ThreadLocalMap的key就是ThreadLocal对象,value就是ThreadLocal对象调用set方法设置的值。
3 内存泄露问题
当我们创建一个泛型为User的ThreadLocal对象并set进一个user后,会在虚拟机栈中存在三个对象引用,分别是Thread对象引用、ThreadLocal对象引用和user对象引用,这三个对象引用都指向堆内存的对象。其中thread对象有一个内部类对象ThreadLocalMap,这个map的每一个entry的key是threadlocal对象,value是user对象。因此对于一个entry对象,有ThreadLocalMap的强引用指向它;然后其中entry对象的key,是ThreadLocal对象的弱引;对于其中的value,是user对象的强引用。
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
所以,当ThreadLocal对象引用没了,那么ThreadLocal对象就没有被强引用指向了,在垃圾回收的时候,ThreadLocalMap的entry的key会被清理掉,但是value不会被清理掉。这样一来,ThreadLocalMap中就会出现key为null的Entry。
假如我们不做任何措施的话,value永远无法被GC回收,这个时候就可能会产生内存泄露。ThreadLocalMap 实现中已经考虑了这种情况,在调用set()、get()、remove()方法的时候,会清理掉key为null 的记录。使用完 ThreadLocal方法后最好再手动调用remove()方法。
4 entry的key为什么被设计为弱引用
因为ThreadlocalMap是和线程绑定在一起的,如果线程没有被销毁,即使我们已经不会再用某个ThreadLocal对象了,由于这个ThreadLocal对象对应的entry还存在,并且key如果是强引用的话,那么就会一直指向ThreadLocal对象,那么这个对象就一直不会被回收,于是就出现了内存泄漏。
为了避免这种情况,只要将key设置为弱引用,那么当发生GC的时候,就会自动将弱引用给清理掉。也就是说:如果ThreadLocal对象不再使用了,那么虚拟机栈中ThreadLocal对象的强引用就会去掉,那么就剩下entry中key的弱引用指向ThreadLocal对象了,很快这个ThreadLocal对象就会被GC回收掉了。