Java多线程——ThreadLocal详解
概念:ThreadLocal用于提供线程局部变量,在多线程环境下可以保证各个线程中的变量独立于其它线程中的变量。
ThreadLocal保证了多线程环境下数据的独立性。
先来看ThreadLocal是怎样使用的:
public class ThreadLocalTest {
private static String commStr;
private static ThreadLocal<String> threadStr
= new ThreadLocal<>();
public static void main(String[] args)
throws InterruptedException {
commStr = "main";
threadStr.set("main");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
commStr = "thread";
threadStr.set("thread");
}
});
thread.start();
thread.join();
System.out.println("commStr:"+commStr);
System.out.println("threadStr:"+threadStr.get());
}
}
// 打印结果:
commStr:thread
threadStr:main
ThreadLocal最常见的使用无非就是get()和set()的实现,那么这些究竟是怎样实现线程独立的呢?
简单的说,每个线程都维护着一个Map集合(ThreadLocalMap),正是由于这个集合的存在,使得线程间可以相互独立。
ThreadLocal的set()方法
先获取当前线程,再将该ThreadLocal和要保存的值value存放在当前线程的ThreadLocalMap中(ThreadLocal作为key,要保存的值value作为value)。
若当前线程还初始化ThreadLocalMap,则先初始化当前线程的ThreadLocalMap,再执行上面的操作。
ThreadLocal的get()方法
先获取当前线程,再将该ThreadLocal对象传入当前线程的ThreadLocalMap中,ThreadLocalMap通过该ThreadLocal(key)查出其所对应的value并返回。
若当前线程的ThreadLocalMap中并没有key为该ThreadLocal的键值对,或当前线程的ThreadLocalMap根本就还没有初始化。
①若没有该key,则将该ThreadLocal和null存入当前线程的ThreadLocalMap中,再将null返回(ThreadLocalMap中value可以为null)
②若ThreadLocalMap没有初始化(肯定没有该key),则先初始化当前线程的ThreadLocalMap,并将该ThreadLocal和null存入ThreadLocalMap,再将null返回。
现在的问题就在于,到底什么才是ThreadLocalMap。
每个线程的ThreadLocalMap是属于线程自己的,ThreadLocalMap中维护的值也是属于线程自己的。这就保证了ThreadLocal类的变量在每个线程中是独立的,在多线程环境下不会相互影响。
ThreadLocalMap内部其实维护了一个哈希表来存储数据,每个键值对都是一个Entry对象,该Entry对象用于存储ThreadLocal对象和value值所构成的键值对。ThreadLocalMap中的key与ThreadLocal对象之间采用弱引用的方式
,为保证ThreadLocal对象与key引用对象能够被GC正常的回收。
ThreadLocalMap对内存泄漏的处理
1、ThreadLocalMap中的结点Entry的key采用弱引用
(key弱引用ThreadLocal对象),当ThreadLocal对象被回收时,ThreadLocalMap中的key引用对该对象的回收没什么影响(该key也会被回收)。
2、此时虽然key被回收后变为null了(ThreadLocalMap中允许key为null的存在),ThreadLocalMap中会出现大量key为null的Entry对象,而此时value并没有得到释放,也有可能存在内存泄漏。因此访问ThreadLocal的set(),get(),remove()方法时,会清除当前线程ThreadLocalMap中所有key==null的value,从而降低内存泄漏的概率。
在使用完ThreadLocal对象后,最好手动调用remove()方法清除数据,防止内存泄漏。