ThreadLocal解析

1、ThreadLocal介绍

ThreadLocal解决线程局部变量统一定义问题,多线程数据不能共享。(InheritableThreadLocal特例除外)不能解决并发问题。解决了:基于类级别的变量定义,每一个线程单独维护自己线程内的变量值(存、取、删的功能)

官方文档给出的解释是:
“该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。”

1.ThreadLocal类封装了getMap()、Set()、Get()、Remove()4个核心方法。
2.通过getMap()获取每个子线程Thread持有自己的ThreadLocalMap实例, 因此它们是不存在并发竞争的。可以理解为每个线程有自己的变量副本
3.ThreadLocalMap中Entry[]数组存储数据,初始化长度16,后续每次都是2倍扩容。主线程中定义了几个变量,Entry[]才有几个key。
4.4.Entry的key是对ThreadLocal的弱引用,当抛弃掉ThreadLocal对象时,垃圾收集器会忽略这个key的引用而清理掉ThreadLocal对象, 防止了内存泄漏。

ThreadLocal核心的机制:
1.每个Thread线程内部都有一个Map。
2.Map里面存储线程本地对象(key)和线程的变量副本(value)
3.Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。

2 核心方法

2.1 get()方法

在这里插入图片描述
里面有一个getmap()方法和setInitialValue()方法

在这里插入图片描述
在这里插入图片描述
get()方法的作用
1.获取当前线程的ThreadLocalMap对象threadLocals
2.从map中获取线程存储的K-V Entry节点。
3.从Entry节点获取存储的Value副本值返回。
4.map为空的话返回初始值null,即线程变量副本为null,在使用时需要注意判断NullPointerException。
5.setInitialValue()作用是如果map不为空,就设置键值对,为空,再创建Map

整个过程首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。

通过这个方法可以得知
1.实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals(ThreadLocalMap对象)中的;
2.threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量

2.2 set()方法和remove()

在这里插入图片描述
俩比较比较简单。remove()就自己看源码吧
1.获取当前线程的成员变量map
2.map非空,则重新将ThreadLocal和新的value副本放入到map中。
3.map空,则对线程的成员变量ThreadLocalMap进行初始化创建,并将ThreadLocal和value副本放入map中。

3 ThreadLocalMap

ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也独立实现
在ThreadLocalMap中,也是用Entry来保存K-V结构数据的。但是Entry中key只能是ThreadLocal对象,这点被Entry的构造方法已经限定死了。
首先我们看看ThreadLocalMap的存储结构—节点。
在这里插入图片描述
Entry是ThreadLocalMap里定义的节点,它继承了WeakReference类,定义了一个类型为Object的value,用于存放塞到ThreadLocal里的值。
这里定义的时候才用了弱引用的方式, 弱引用比软引用更加弱一些,如果一个对象没有强引用链可达,那么一般活不过下一次GC。当某个ThreadLocal已经没有强引用可达,则随着它被垃圾回收,在ThreadLocalMap里对应的Entry的键值会失效,这为ThreadLocalMap本身的垃圾清理提供了便利。
在这里插入图片描述
里面有几个参数和方法比较简单,初始化数量也是16 和hashMap中的一样
ThreadLocalMap维护了Entry环形数组,数组中元素Entry的逻辑上的key为某个ThreadLocal对象(实际上是指向该ThreadLocal对象的弱引用),value为代码中该线程往该ThreadLoacl变量实际塞入的值

第一个构造方法:
在这里插入图片描述
先对table进行初始化,默认大小为INITIAL_CAPACITY。然后计算第一对键值对要存放的位置,即在table中的下标,然后为这个键值对在数组相应的下标下创建一个Entry对象,最后设置阈值。在这里它的阈值为table长度的2/3。

在这里插入图片描述
改私有的构造方法是由ThreadLocal里的createInheritedMap()中调用的,这个方法会由Thread里的构造方法来调用(在Thread类里的init()方法来调用,而init()方法只被Thread里的构造方法调用)

set方法()

在这里插入图片描述

在set()方法中,首先通过key(threadLocal)中的hashc值与table的总长度取模,然后根据取模后的值作为下标,找到table当中下标为此值的entry,判断该entry是否存在。如果存在,判断entry里的key与value如果与当前要保存的key与value相同的话就不保存直接返回。如果entry里的key为null的话,就替换为当前要保存的key与value。否则就是碰撞的情况了,这时就调用nextIndex()方法计算下一个坐标。
如果计算后的坐标获取到的entry为null,就new一个Entry对象并保存进去,然后调用cleanSomeSlots()对table进行清理,如果没有任何Entry被清理,并且表的size超过了阈值,就会调用rehash()方法。
cleanSomeSlots()会调用expungeStaleEntry清理陈旧过时的Entry。rehash则会调用expungeStaleEntries()方法清理所有的陈旧的Entry,然后在size大于阈值的3/4时调用resize()方法进行扩容。

getEntery()方法

在这里插入图片描述
getEntry()方法通过计算出的下标从table中取出entry,如果取得的entry为null或它的key值不相等,就调用getEntryAfterMiss()方法,否则返回。

而在getEntryAfterMiss()是当通过key与table的长度取模得到的下标取得entry后,entry里没有该key时所调用的。这时,如果获取的entry为null,即没有保存,就直接返回null,否则进入循环不,计算下一个坐标并获取对应的entry,并且当key相等时(表明找到了之前保存的值)返回entry,或是entry为null时退出循环,并返回null。

remove方法

在这里插入图片描述
如果取得的entry的key与ThreadLocal相同,就调用Entry的clear方法把弱引用设置为null,然后调用expungeStaleEntry对table进行清理

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值