天天看点

ThreadLocal应用及源码分析

ThreadLocal

ThreadLocal 的作用是:提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度,降低耦合性。

方法声明

描述

ThreadLocal()

创建ThreadLocal对象

public void set( T value)

设置当前线程绑定的局部变量

public T get()

获取当前线程绑定的局部变量

public void remove()

移除当前线程绑定的局部变量

简单使用:

ThreadLocal应用及源码分析
ThreadLocal应用及源码分析

这样可以很好的解决多线程之间数据隔离的问题,用synchronized加锁也可以实现,但synchronized侧重的是多个线程之间访问资源的同步性,而ThreadLocal侧重的是每个线程之间的数据隔离。

synchronized

原理

同步机制采用'以时间换空间'的方式, 只提供了一份变量,让不同的线程排队访问

ThreadLocal采用'以空间换时间'的方式, 为每一个线程都提供了一份变量的副本,从而实现同时访问而相不干扰

侧重点

多个线程之间访问资源的同步性

多线程中让每个线程之间的数据相互隔离

涉及到数据传递和线程隔离的场景,可以考虑用ThreadLocal来解决:转账案例,涉及两个DML操作: 一个转出,一个转入。这些操作是需要具备原子性的。所以这里就需要操作事务,来保证转出和转入操作具备原子性。开启事务的注意两点:

为了保证所有的操作在一个事务中, 使用的连接必须是同一个: service层开启事务的connection需要跟dao层访问数据库的connection保持一致。

线程并发情况下, 每个线程只能操作各自的 connection。

用ThreadLocal的解决方案:在获取Connection连接的JdbcUtils工具类加入ThreadLocal,代码如下:

可以看出使用ThreadLocal的好处:

传递数据 : 保存每个线程绑定的数据,在需要的地方可以直接获取, 避免参数直接传递带来的代码耦合问题

线程隔离 : 各线程之间的数据相互隔离却又具备并发性,避免同步方式带来的性能损失

jdk8以前:

ThreadLocal应用及源码分析

jdk8之前使用ThreadLocal来维护一个ThreadLocalMap,以线程作为key

jdk8以后:

ThreadLocal应用及源码分析

jdk8之后使用Thread来维护一个ThreadLocalMap,以ThreadLocal作为key

这样涉及的好处:

(1) 每个<code>Map</code>存储的<code>Entry</code>数量就会变少,因为jdk8之前的存储数量由<code>Thread</code>的数量决定,现在是由<code>ThreadLocal</code>的数量决定。

(2) 当<code>Thread</code>销毁之后,对应的<code>ThreadLocalMap</code>也会随之销毁,能减少内存的使用。

protected T initialValue()

返回当前线程局部变量的初始值

首先调用<code>Thread.currentThread()</code>方法获取当前线程对象,然后根据当前线程获取维护的<code>ThreadLocalMap</code>对象;如果获取的<code>Map</code>不为空,则在Map中以<code>ThreadLocal</code>的引用作为key,调用<code>getEntry</code>获取对应的存储实体,如果Entry不为空,获取对应的 value值。如果Map为空或者Entry为空,则调用<code>setInitialValue()</code>方法。setInitialValue()方法里,调用<code>initialValue()</code>方法获取初始化值value,然后判断当前线程是否有<code>ThreadLocalMap</code>,map存在,调用<code>set</code>设置Entry;map不存在则调用<code>createMap()</code>进行ThreadLocalMap对象的初始化,并将此<code>entry</code>作为第一个值存放至ThreadLocalMap中。

​ A. 首先获取当前线程,并根据当前线程获取一个ThreadLocalMap

​ B. 如果获取的Map不为空,则将参数设置到Map中(当前ThreadLocal的引用作为key)

​ C. 如果Map为空,则调用createMap给该线程创建 Map,并设置初始值

A. 首先获取当前线程,并根据当前线程获取一个ThreadLocalMap

B. 如果获取的Map不为空,则移除当前ThreadLocal对象对应的entry

(1) 这个方法是一个延迟调用方法,在set方法还未调用而先调用了get方法时才执行,并且仅执行1次。

(2)这个方法直接返回一个<code>null</code>。

(3)如果想要一个除null之外的初始值,可以重写此方法。(备注: 该方法是一个<code>protected</code>的方法,显然是为了让子类覆盖而设计的)

ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也是独立实现。

ThreadLocal应用及源码分析

成员变量

Entry

在ThreadLocalMap中,也是用Entry来保存K-V结构数据的。但是Entry中key只能是ThreadLocal对象,这点被Entry的构造方法已经限定死了;

另外,Entry继承WeakReference,使用弱引用,可以将ThreadLocal对象的生命周期和线程生命周期解绑,持有对ThreadLocal的弱引用,可以使得ThreadLocal在没有其他强引用的时候被回收掉,这样可以避免因为线程得不到销毁导致ThreadLocal对象无法被回收

hash冲突的解决

<code>&amp; (INITIAL_CAPACITY - 1)</code>,这是取模的一种方式,对于2的幂取模,用此代替<code>%(2^n)</code>,这也就是为啥容量必须为2的幂

<code>firstKey.threadLocalHashCode</code>:

这里定义了一个AtomicInteger类型,每次获取当前值并加上HASH_INCREMENT,<code>HASH_INCREMENT = 0x61c88647</code>,这个值是32位整型上限2^32-1乘以黄金分割比例0.618....的值2654435769,用有符号整型表示就是-1640531527,去掉符号后16进制表示为0x61c88647,目的就是为了让哈希码能均匀的分布在2的n次方的数组<code>Entry[] table</code>中。

线性探测法:

该方法一次探测下一个地址,直到有空的地址后插入,若整个空间都找不到空余的地址,则产生溢出。假设当前table长度为16,也就是说如果计算出来key的hash值为14,如果table[14]上已经有值,并且其key与当前key不一致,那么就发生了hash冲突,这个时候将14加1得到15,取table[15]进行判断,这个时候如果还是冲突会回到0,取table[0],以此类推,直到可以插入。<code>可以把table看成一个环形数组</code>

ThreadLocalMap的set():