线程进阶-2 ThreadLocal

一.简单说一下ThreadLocal

1.ThreadLocal是一个线程变量,用于在并发条件下,为不同线程提供相互隔离的变量存储空间。在多线程并发的场景下,每个线程往ThreadLocal中存的变量都是相互独立的。

2.基本方法

(1)set(Object v):将变量与当前线程绑定。

(2)get():获取当前线程绑定的变量

(3)remove():移除当前线程绑定的变量

二.synchronized和ThreadLocal的区别

synchronized和ThreadLocal都是用于解决多线程并发访问共享变量的情况。

1.原理:

(1)synchronized是通过加锁的方式,只提供一份变量,每次只让一个线程访问该变量;上一个线程对变量的访问结果会影响下一个访问变量的线程

(2)ThreadLocal是对变量进行拷贝,每一个线程都往自己的空间中存一份共享变量的副本,多线程可同时访问各自的副本变量。且由于线程空间相互隔离,线程对副本变量的修改不会对其他线程产生影响

2.侧重点:

(1)synchronized主要用于多线程间访问资源的同步,保证了并发编程的原子性和可见性;但并发效率下降。

(2)ThreadLocal主要用于同一个线程在上下执行逻辑间传递数据,保证了多线程间数据的隔离。

三.说一下ThreadLocal的内部结构

1.jdk1.7:

(1)一个ThreadLocal内部有一个Map(ThreadLocalMap)用于存储数据。

(2)ThreadLocalMap的key是线程Thread,value是该线程绑定的变量副本。

(3)线程每次要访问变量,都要到ThreadLocal中,以自己为映射找到对应的变量。

2.jdk1.8:

(1)每一个线程Thread内部都有一个Map(ThreadLocalMap)用于存储数据。

(2)ThreadLocalMap的key是ThreadLocal,value是该线程绑定的副本变量。

(3)线程每次要访问变量,都要到自己的空间中,以当时绑定的ThreadLocal对象作为key映射,找到对应的变量。

3.jdk1.8把ThreadLocalMap存在Map中的好处

(1)降低哈希冲突的概率。jdk1.8的ThreadLocalMap是用ThreadLocal对象作为key,jdk1.7的ThreadLocalMap是用Thread对象作为key,通常程序中ThreadLocal对象的数量要明显少于Thread对象的数量,则ThreadLocalMap要存储的元素就减少,发生哈希冲突的概率就会下降。

(2)提高内存利用率。ThreadLocalMap在Thread中,则当Thread销毁时对应的ThreadLocalMap也会被销毁。但若是ThreadLocalMap在ThreadLocal中,则只要ThreadLocal还存在,即使Thread都销毁了,对应的ThreadLocalMap仍然存在占据空间。

四.说一下ThreadLocal的底层原理

1.set(T value):

(1)获取当前线程

(2)如果当前线程的ThreadLocalMap不为空,则以键值对ThreadLocal:value存入。

(3)如果当前ThreadLocalMap为空,则先创建一个ThreadLocalMap,再将ThreadLocal:value存入。

2.get():

(1)获取当前线程

(2)如果当前线程的ThreadLocalMap不为空,则以ThreadLocal为键,索引到对应节点Entry e

(3)如果e不为null,则直接将e返回

(4)如果当前线程的ThreadLocalMap为null 或 获取到的e为null,则调用setInitialMap()方法:若ThreadLocalMap为null,则创建ThreadLocalMap,并将键值对ThreadLocal:initialValue()存入后返回;如果e为null,则将键值对ThreadLocal:initialValue()存入后返回。即最终返回的都是initialValue()。

3.initialValue():

(1)直接返回一个null

(2)也就是说对于get()方法中ThreadLocalMap为null或者索引到的Entry为null时,都会以ThreadLocal:null存入,并将null返回;只有当第一次调用set(T value)时才会将null值覆盖。

(3)可以通过重写initialValue()方法,设置存入的初始值不为null。

4.remove():

(1)获取当前线程

(2)如果当前线程的ThreadLocalMap不为null,则将索引到的Entry移除。

五.说一下ThreadLocal的内存泄露情况

1.ThreadLocalMap的引用情况:

(1)ThreadLocalMap是Thread的一个属性,Thread对ThreadLocalMap是强引用;ThreadLocalMap对里面的每一个节点Entry也是强引用。即存在强引用链:Thread——ThreadLocalMap——Entry。

(2)ThreadLocal是ThreadLocalMap的键,ThreadLocalMap对ThreadLocal是弱引用。

2.如果ThreadLocalMap对ThreadLocal是强引用:

(1)当栈中其他对ThreadLocal的引用销毁,由于ThreadLocalMap对ThreadLocal是强引用,因此ThreadLocal在堆中无法被回收,有OOM的风险。

(2)栈中其他对ThreadLocal的引用销毁,则ThreadLocalMap中该ThreadLocal对应的Entry也就没有意义;但由于ThreadLocalMap对Entry是强引用,因此该Entry无法被回收,有OOM的风险。

3.如果ThreadLocalMap对ThreadLocal是弱引用:

(1)当栈中其他对ThreadLocal的引用销毁,由于ThreadLocalMap对ThreadLocal是弱引用,因此ThreadLocal在堆中可以被回收。回收后ThreadLocalMap对应的键就会变回null,但值Entry仍然存在。

(2)ThreadLocal被回收,则ThreadLocalMap中该ThreadLocal对应的Entry也就没有意义;但由于ThreadLocalMap对Entry是强引用,因此该Entry无法被回收,有OOM的风险。

4.可以看出:无论ThreadLocalMap对ThreadLocal是强引用还是弱引用,都会有OOM的风险,因为ThreadLocalMap会导致OOM的根本原因是存在强引用链Thread——ThreadLocalMap——Entry,导致Entry的生命周期和Thread一样长,若不手动删除对应的Entry就会有OOM的风险。

5.因此使用ThreadLocal时为了避免OOM,一定要调用remove()手动删除对应的Entry。

六.无论ThreadLocalMap对ThreadLocal是强引用还是弱引用都会导致OOM,那选择弱引用的原因是什么?

1.ThreadLocalMap对ThreadLocal是弱引用,则当外部没有其他对ThreadLocal的强引用时,堆中的ThreadLocal可以被顺利回收。提高内存利用率。

2.ThreadLocal被回收后,其在ThreadLocalMap中对应的键会变成null,变成了幽灵键。而ThreadLocal的set()、get()、remove()等方法在执行时会进行迭代检查,若检测到幽灵键则会将对应的Entry移除。降低了OOM的风险。

七.ThreadLocalMap中解决哈希冲突的方式是什么?

线性探测法

1.若将ThreadLocal作为key哈希映射到的槽位已被占用,且占用槽位的ThreadLocal和当前ThreadLocal不是同一个对象,则当前索引加一,继续判断下一个。若索引到达最后一个仍不满足,则索引归零,重新开始往后判断,直到找到一个槽位为null,可存储;或者找到占用槽位的ThreadLocal和当前ThreadLocal为同一个对象,可覆盖。

2.ThreadLocalMap的扩容阈值threshold是length*(2/3),因此不会出现把整个Map都遍历一遍仍然找不到槽位存储的情况。

  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值