ThreadLocal简介与原理

Posted june0816

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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的对象

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. ThreadLocalMap在get/set时检查Entry,如果key为null,进行删除,可以减少内存泄漏,但还是会存在内存泄漏风险

 

五、最佳实践

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

 

 

 

以上是关于ThreadLocal简介与原理的主要内容,如果未能解决你的问题,请参考以下文章

深入浅出多线程编程实战ThreadLocal详解(介绍使用原理应用场景)

ThreadLocal原理及使用示例

深入ThreadLocal原理剖析

ThreadLocal源码理解原理

ThreadLocal的使用及原理分析

008-ThreadLocal原理分析