ThreadLocal、ThreadLocalMap、Thread
什么是ThreadLocal?
官方解释:ThreadLocal是一个本地线程副本变量工具类,主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰。
白话:ThreadLocal就是给每个线程储存值,且各个线程之间相互隔离。
ThreadLocal原理
get()方法
get()源码:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
可以看出,它是通过获取当前线程,再通过当前线程获取当前线程管理的静态变量ThreadLocalMap,再通过key为ThreadLocal往map中取值。
set()方法
set()源码:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
获取当前线程,通过当前线程获取线程对应的ThreadLocalMap,再通过key为ThreadLocal往map中调用set方法。
ThreadLocal、Thread、ThreadLocalMap三者的关系
每一个Thread都对应一个ThreadLocalMap,该map在线程创建时已经创建出来,生命周期与Thread相同。ThreadLocalMap中存放Entry结点(<k,v>结构),每一个entry结点对应一个ThreaLocal。而ThreadLocal是Thread储存在ThreadLocalMap中的value的key,获取那一个结点。每一个ThreadLocal能够储存一个value,调用多次set方法会发送覆盖。
Hash冲突问题
因为根据ThreadLocal寻找entry结点这一过程涉及到hash算法,当得到的hash值发生冲突时(概率小),会采用线性探测寻找下一个位置的方法解决,效率很低。因此,建议每个线程只存一个变量(只有一个ThreadLocal)就不存在Hash冲突的问题。
内存泄漏问题
为什么会发送内存泄漏 ?
内存泄漏的定义:内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。
从上面的分析可知,ThreadLocalMap的生命周期和Thread一样,那如果Thread是在线程池中,则是能够持续存活很长的一段时间的。有了这个前提,我们继续。而ThreadLocalMap中的key(也就是ThreadLocal)是一个弱引用,它在JVM下一次的GC中是会被回收的。而我们自己设置的存放在map中的value是一个强引用,JVM无法回收。如果key被回收了,我们也无法通过key来手动回收了,就发生了内存泄漏。得等到ThreadLocalMap被回收,也就是Thread被销毁。
如何解决?
①Java8中已经做了一些优化如,在ThreadLocal的get()、set()、remove()方法调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value,并将整个Entry设置为null,利于下次内存回收。
②良好的编码习惯:加try,catch,finally限制。在try里面进行ThreadLocal.set(),在finally里面进行ThreadLocal.remove()手动删除entry。
ThreadLocal的应用例子
public class main {
private static ThreadLocal<String> sThreadLocal = new ThreadLocal<>();
public static void main(String args[]) {
sThreadLocal.set("这是在主线程中");
System.out.println("线程名字:" + Thread.currentThread().getName() + "---" + sThreadLocal.get());
//线程a
new Thread(new Runnable() {
@Override
public void run() {
sThreadLocal.set("这是在线程a中");
System.out.println("线程名字:" + Thread.currentThread().getName() + "---" + sThreadLocal.get());
}
}, "线程a").start();
//线程b
new Thread(new Runnable() {
@Override
public void run() {
sThreadLocal.set("这是在线程b中");
System.out.println("线程名字:" + Thread.currentThread().getName() + "---" + sThreadLocal.get());
}
}, "线程b").start();
//线程c
new Thread(() -> {
sThreadLocal.set("这是在线程c中");
System.out.println("线程名字:" + Thread.currentThread().getName() + "---" + sThreadLocal.get());
}, "线程c").start();
}
}
输出:
线程名字:main---这是在主线程中
线程名字:线程b---这是在线程b中
线程名字:线程a---这是在线程a中
线程名字:线程c---这是在线程c中