对ThreadLocal的理解个人

ThreadLocal提供一个方便的方式,可以根据不同的线程存放一些不同的特征属性,可以方便的在线程中进行存取;

简单一句话,自己的线程里的东西自己玩,别人不能动。达到隔离的效果,就不存在线程安全的问题了;

举例子:1000个线程任务打印时间:

 

结果:用dateFormat的时候,会出现打印出相同的时间的结果,原因是所有线程共用一个SimpleDateFormat对象的时候,它发生了线程安全的问题,而用dateFormatThreadLocal解决了;

原理;先沾出来ThreadLocal的源码

 

 简化下就如下:

Thread.currentThread().threadLocals.set(this,value);
Thread.currentThread()获取当前的线程
threadLocals就是每个线程对象中用于存放局部对象的map

threadLocals可以简单粗暴的理解为,每一个线程都自带一个map,map的key为dateFormatThreadLocal,value为SimpleDateFormat对象;这样这个对象就隔离了,自己用自己的;

ThreadLocal就是一个标记的作用,当我们在线程中使用ThreadLocal的set()或者get()方法时,其实是在操作我们线程自带的threadLocals这个map,多个线程的时候自然就有多个map,这些map互相独立,但是,这些map都是根据一个ThreadLocal对象(因为它是静态的)来作为键存放。

  这样可以在多个线程中,每个线程存放不一样的变量,我们通过一个ThreadLocal对象,在不同的线程(通过Thread.currentThread()获取当前线程)中得到不同的值(不同线程的threadLocals不一样)。

  为什么threadLocals要是一个map呢?

  因为我们可能会在一个类中声明多个ThreadLocal的实例,这样就有多个标记,所以要使用map对应。

 ThreadLocal这么吊了,用它时候要注意了,他可能产生内存泄漏!!

threadLocal,threadLocalMap,entry之间的关系如下图所示:

 

 

 

上图中,实线代表强引用,虚线代表的是弱引用,如果threadLocal外部强引用被置为null(threadLocalInstance=null)的话,threadLocal实例就没有一条引用链路可达,很显然在gc(垃圾回收)的时候势必会被回收,因此entry就存在key为null的情况,无法通过一个Key为null去访问到该entry的value。同时,就存在了这样一条引用链:threadRef->currentThread->threadLocalMap->entry->valueRef->valueMemory,导致在垃圾回收的时候进行可达性分析的时候,value可达从而不会被回收掉,但是该value永远不能被访问到,这样就存在了内存泄漏。当然,如果线程执行结束后,threadLocal,threadRef会断掉,因此threadLocal,threadLocalMap,entry都会被回收掉。可是,在实际使用中我们都是会用线程池去维护我们的线程,比如在Executors.newFixedThreadPool()时创建线程的时候,为了复用线程是不会结束的,所以threadLocal内存泄漏就值得我们关注。

为什么使用弱引用?

通过threadLocal,threadLocalMap,entry的引用关系看起来threadLocal存在内存泄漏的问题似乎是因为threadLocal是被弱引用修饰的。那为什么要使用弱引用呢?

如果使用强引用

假设threadLocal使用的是强引用,在业务代码中执行threadLocalInstance==null操作,以清理掉threadLocal实例的目的,但是因为threadLocalMap的Entry强引用threadLocal,因此在gc的时候进行可达性分析,threadLocal依然可达,对threadLocal并不会进行垃圾回收,这样就无法真正达到业务逻辑的目的,出现逻辑错误

如果使用弱引用

假设Entry弱引用threadLocal,尽管会出现内存泄漏的问题,但是在threadLocal的生命周期里(set,getEntry,remove)里,都会针对key为null的脏entry进行处理。

从以上的分析可以看出,使用弱引用的话在threadLocal生命周期里会尽可能的保证不出现内存泄漏的问题,达到安全的状态。




总结下用threadlocal的最佳实践:

  • 每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
  • 在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就清理。

引用:https://www.jianshu.com/p/dde92ec37bd1
posted @ 2020-04-11 21:48  小哥的吃喝玩乐  阅读(167)  评论(0编辑  收藏  举报