ThreadLocal

ThreadLocal是一个数据结构,有点像HashMap,可以保存key-value键值对,但是一个ThreadLocal只能保存一个,并且各个线程的数据互不干扰。ThreadLocal为变量在每个线程中都创建一个副本。

在线程1中初始化了一个ThreadLocal对象localName,并通过set方法,保存一个占小狼的值,get方法可以拿到之前设的值,但是如果在线程2中,拿到的将是一个null。

   下面set和get的源码:

   

 

可以发现每个线程中都有一个ThreadLocalMap数据,当执行set方法时,其值保存在当前线程的threadLocalMap变量中,在第一次调用set方法时,创建ThreadLocalMap。Set方法本质上是调用了ThreadLocalMap的set方法。当执行get方法,是从当前线程的ThreadLocalMap变量获取。所以在线程1中set的值,对线程2来说是摸不到的。

什么是ThreadLocalMap?

ThreadLocalMap类似HashMap,但并没有实现Map接口。ThreadLocalMap中也是初始化一个大小16的Entry数组,Entry对象用来保存每一个key-value对。这里的key是ThreadLocal对象的引用。Entry继承于WeakReference,Entry中没有next字段,所以不存在链表的情况了。

既然一个线程只对应一个 Thread 对象,那么一个线程中也就只有一个 ThreadLocalMap 对象。不论程序中创建了多少 ThreadLocal 对象,在同一个线程中都会访问到这个 Map。

 ThreadLocal可能导致内存泄漏:先看Entry的实现:

    ThreadLocal在保存一个value时,会在ThreadLocalMap中的数组插入一个Entry对象,在ThreadLocalMap的实现中,key被保存到了WeakReference对象中。这就导致了ThreadLocal在没有外部强引用时,发生GC时会被回收,这样一来,ThreadLocalMap中就会出现key是null的Entry对象,再也没办法访问这些key为null的Entry的value,发生内存泄漏。除非这个线程被回收,否则这个value都是不能回收的。那么怎么应对内存泄漏呢?在调用ThreadLocal的get和set时,清除ThreadLocalMap中key为null的Entry对象,这样对应的value就没有Gc Roots可达了,下次GC的时候就会被回收。当然调用remove方法,删除对应Entry对象,肯定可以。

  为什么Entry的key要搞成弱引用呢?看这个场景:

  public void func1() {
    ThreadLocal tl = new ThreadLocal<Integer>(); //line1
    tl.set(100); //line2
    tl.get(); //line3
  }

line1新建了一个ThreadLocal对象,t1是强引用指向这个对象;line2调用set()后,新建一个Entry,通过源码可知entry对象里的 k是弱引用指向这个对象。如图:

 

  当func1方法执行完毕后,栈帧销毁,强引用t1也就没有了,但此时线程的ThreadLocalMap里某个entry的k引用还指向这个ThreadLocal。若这个k引用是强引用,那么发生GC时本线程的ThreadLocalMap还持有ThreadLocal的强引用,会导致ThreadLocal不会被回收,从而导致内存泄漏。使用弱引用,就可以使ThreadLocal对象在方法执行完毕后顺利被回收,而且在entry的k引用为null后,再调用get,set或remove方法时,就会尝试删除key为null的entry,可以释放value对象所占用的内存。

  ThreadLocal工作机制:

1)Thread类中有一个成员变量属于ThreadLocalMap类,该map的key是ThreadLocal对象,value是ThreadLocal调用set方法设置的值。为什么key是ThreadLocal对象?因为每个线程可能有多个threadLocal变量,可以定义longLocal和stringLocal。

2)当为ThreadLocal类的对象set值时,首先获得当前线程的ThreadLocalMap类属性,然后以ThreadLocal类对象为key,设定value。get值时类似。

3) ThreadLocal变量的活动范围为某线程,是该线程专有的。

   如果一个线程有多个ThreadLocal对象,每一个ThreadLocal对象是如何区分的呢?对于每个ThreadLocal对象,都有个final修饰的int型的threadLocalHashCode,对于基本数据类型,它在初始化后就不可以进行修改,所以可以唯一确定一个ThreadLocal对象

   ThreadLocal和其他同步机制一样,都是为了解决多线程中对同一变量的访问冲突。ThreadLocal和同步机制面向的问题领域不同,后者是为了同步多个线程对相同资源的并发访问,是多个线程之间进行通信的有效方式。而前者是为了隔离多个线程的资源共享,从根本上避免多个线程之间对共享资源的竞争。所以如果多个线程需要共享资源,以达到线程之间的通信目的,就使用同步机制。如果为了隔离多个线程的资源共享,就使用ThreadLocal

  hash冲突怎么处理?

  采用黄金分割数来做hashcode,使得哈希结果尽量均匀。并采用线性探测而非散列表的方式来解决哈希冲突 

 

  threadlocal跨线程传递数据的方式:

  InheritableThreadLocal它能够支持跨线程传递数据,但也仅限于父线程给子线程来传递数据。InheritableThreadLocal支持子线程访问父线程中本地变量的原理是:创建子线程时将父线程中的本地变量值拷贝了一份到子线程中,拷贝的时机是子线程创建时。

  在实际开发中,多线程就离不开线程池的使用,因为线程池能够复用线程,减少线程的频繁创建与销毁。倘若线程池使用InheritableThreadLocal来传递数据,那么线程池中的线程拷贝的数据始终来自于第一个提交任务的外部线程,这样非常容易造成线程本地变量混乱。此时就要借助阿里开源的TransmittableThreadLocal。

  TransmittableThreadLocal:解决了线程池无法传递线程本地副本的问题

posted @   MarkLeeBYR  阅读(71)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示