zoukankan      html  css  js  c++  java
  • 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

     

  • 相关阅读:
    python基础 列表生成式
    docker 基础
    xpath例子
    redis删除以什么开头的key
    redis 关闭持久化
    python爬虫 保存页面
    python爬虫操作cookie
    SQl函数的写法
    加料记录(大屏幕)
    ios 调试
  • 原文地址:https://www.cnblogs.com/june0816/p/7063902.html
Copyright © 2011-2022 走看看