ThreadLocal真那么复杂吗?网抑云年代记

生而为人 我很抱歉
网抑云年代平淡的一天 我准备啃下ThreadLocal这块硬骨头 建议打开网抑云 伴随着歌声阅读。

什么是ThreadLocal?

ThreadLocal对象可以提供当前线程的局部变量,每个线程Thread拥有一份自己的副本变量,多个线程间互不干扰,实现了数据隔离。
具体使用如下:

ThreadLocal<String> threadLocal=new ThreadLocal<>();
threadLocal.set("这是由我所统率的战场!");
String s=threadLocal.get();
System.out.print(s);
threadLocal.remove();

ThreadLocal的应用场景

  1. Spring实现事务隔离级别
    Spring采用ThreadLocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接(Connection),采用这种方式可以是业务层使用事务时不需要感知并管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换、挂起和恢复。
  2. 一个线程中多方法调用传递对象
    我们在调用某个接口时,可能它横跨了若干方法去调用,需要传递的对象,也就是上下文(Context),它是一种状态,经常是用户身份、任务信息等,就会存在过渡传参的问题。
    使用类似责任链模式,给每个方法增加一个context参数非常麻烦,如果调用链有无法修改源码的第三方库,对象参数就无法传进去了,我们在此处可以使用ThreadLocal的set方法来保存,需要使用的时候直接get()即可,记得最后一次使用后要记得remove防止内存泄露。
  3. SimpleDataFormat的使用
    当使用SimpleDataFormat的parse方法时,会先调用其内部对象Calendar的clear()方法再调用add()方法,如果在多线程的情况下,某线程调用了add()方法而另一线程调用了clear()方法,parse()方法的结果就会不正确。
    解决方法是每个线程都new一个属于自己的SimpleDataFormat,我们可以使用线程池和ThreadLocal包装SimpleDataFormat,再调用initialValue让每个线程都有自己的SimpleFormat对象就可以了。既解决了线程安全的问题,又提高了性能。

ThreadLocal的实现原理

每个线程Thread都维护了自己的threadLocals变量,也可以简单地认为每个线程都有自己的一个ThreadLocalMap。所以在每个线程创建ThreadLocal的时候,实际上数据是存在自己线程Thread的threadLocals变量里面的,别人没办法拿到,从而实现了隔离。
而ThreadLocalMap其实也是一种key+value类似于HashMap的数据结构,它的key是ThreadLocal的一个弱引用,而value则是你要存入的值。

ThreadLocalMap

ThreadLocalMap其实不是保存在ThreadLocal对象中的,它是保存在当前线程Thread中的,它的结构有点类似于HashMap结构,但HashMap是由数组+链表实现的,而ThreadLocal并没有链表结构。
ThreadLocal需要数组来存放一个线程中的多个ThreadLocal来存放多个不同类型的对象,至于没有链表解决hash冲突的问题,当hash值计算出来之后,会判断一下,当前位置是否为空,若为空,则生成一个Entry对象放在该位置上;若不为空,判断当前位置Entry对象的key是否为即将设置的key,如果是,直接刷新value;如果不是,则继续往后查找下一个空位置。
在get的时候,也会根据ThreadLocal对象的hash值,定位到数组中,然后判断key是否一致,如果不一致,再往后找。因此,如果hash冲突严重,效率还是很低的。

内存泄露问题

发生内存泄漏,是因为Entry的key是ThreadLocal对象的弱引用,当ThreadLocal没有外部的强引用时,发生GC后会被回收,如果创建ThreadLocal的线程一直运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄漏。
解决方法是在使用完之后将该ThreadLocal对象remove即可。remove的源码就是把对应的值全部置空,这样下次GC时会自动把他们回收掉。

共享线程的ThreadLocal数据

使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值,我们在主线程中创建一个InheritableThreadLocal的实例,然后在子线程中得到这个InheritableThreadLocal实例设置的值。

private void test() {
    
        
final ThreadLocal threadLocal = new InheritableThreadLocal();       
threadLocal.set("父线程的tl");    
Thread t = new Thread() {
    
            
    @Override        
    public void run() {
    
                
      super.run();            
      System.out.print("子线程可以访问"+threadlocal.get());        
    }    
  };          
  t.start(); 
} 

此文章来自本人阅读多篇博客整理,以下为参考文章:
JavaGuide哥微信公众号的文章
Guide哥的github对ThreadLocal源码的深度解析
掘金某专栏

猜你喜欢

转载自blog.csdn.net/fucccck_ly/article/details/107748424