ThreadLocal简介与原理
一、引入场景
1. 打印方法执行的耗时
public void service(){ before(); doSomething(); after(); }
2. 在before和after记录当前时间,两者相减得到doSomething()的耗时
private long startTime; // 定义变量开始时间 public void before(){ startTime=System.CurrentTimeMills(); // 记录开始时间 } public void doSomething(); public void after(){ endTime=System.CurrentTimeMills(); // 记录结束时间 costTime=endTime-startTime; // 计算耗时 }
3. 多线程下,共享变量开始时间startTime可能会被别的线程改写,不是线程安全的
4. 多线程解决方法:
a. 加锁,性能不好
b. ThreadLocal
二、概念
1. ThreadLocal线程本地变量,即把共享变量拷贝一份放在当前线程内,变成线程私有的变量
2. 变量的生命周期在当前线程范围内,别的线程不能访问,线程间数据隔离
3. 场景:当一个变量需要与线程关联,并且线程后面还会用到该值,比如traceId
三、ThreadLocal原理
1. Thread类里有一个变量threadLocals
2. threadLocals变量的类型是ThreadLocalMap
3. ThreadLocalMap也是一个Map,这个Map名字叫做Entry, key是ThreadLocal类的实例,value是设置的值
4. 如果要存储多个值,需要多个ThreadLocal,但建议一个线程只存一个变量,多个变量会引起hash冲突
Thread.ThreadLocalMap<ThreadLocal, Object>
4. 代码
private static ThreadLocal<Long> startTimeThreadLocal=new ThreadLocal<>(); // 创建ThreadLocal对象 public static void before(){ startTimeThreadLocal.set(System.currentTimeMills()); // 设置值,在当前线程里,threadLocals变量Map的key为startTimeThreadLocal,value为System.currentTimeMills() } public static void doSomething(){ } public static void after(){ long endTime=System.currentTimeMills(); long costTime=endTime-startTimeThreadLocal.get(); }
5. threadLocal.set()源码
public void set(T value) { Thread t = Thread.currentThread(); // 获取当前线程 ThreadLocalMap map = getMap(t); // set的时候生成一个ThreadLocalMap if (map != null) map.set(this, value); else createMap(t, value); } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); // 创建ThreadLocalMap时,设置当前线程的threadLocals变量,把threadLocal对象作为key }
四、ThreadLocal内存泄露问题
1. ThreadLocalMap的Entry(即key+value)中,key是ThreadLocal对象,值是Object对象
2. Entry继承WeakReference弱引用,只有key是弱引用,value不是,所以每次GC都会回收key的对象
为什么要设置为弱引用?因为想尽快清除ThreadLocal的对象,回收内存,否则在线程结束前都会占用内存空间
static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } }
3. 当线程没有结束,但是ThreadLocal已经被回收,即key的对象被回收,而value的对象没被回收,对象会占用内存,则可能导致线程中存在ThreadLocalMap<null, Object>的键值对,造成内存泄露,此时只是暂时的内存泄漏
4. 当使用线程池中,线程不会被销毁,永远造成了内存泄漏
5. ThreadLocalMap在get/set时检查Entry,如果key为null,进行删除,可以减少内存泄漏,但还是会存在内存泄漏风险
6. 调用remove()方法可以解决内存泄漏
五、最佳实践
1. JDK建议ThreadLocal定义为static,类的所有实例都可以共享这个ThreadLocal,避免了产生浪费
2. static变量的生命周期与类一样,只要类存在,static变量也存在,所以必须要remove,防止内存泄漏
3. 在finally里主动调用ThreadLocal的remove()方法清除线程共享变量
六、Netty中的FastThreadLocal,性能是jdk中的ThreadLocal的3倍
参考:
https://www.cnblogs.com/xzwblog/p/7227509.html
https://www.jianshu.com/p/98b68c97df9b
https://www.cnblogs.com/yxysuanfa/p/7125761.html
https://blog.csdn.net/qq_42862882/article/details/89820017