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<nullObject>的键值对,造成内存泄露,此时只是暂时的内存泄漏

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://mp.weixin.qq.com/s?__biz=Mzg2NzA4MTkxNQ==&mid=2247485096&amp;idx=1&amp;sn=a1c7606296e7cb08d440933e1109a140&source=41#wechat_redirect

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

 

 

 

posted @ 2019-10-26 20:12  牧云文仔  阅读(309)  评论(0编辑  收藏  举报