ThreadLocal
ThreadLocal
有一个ThreadLocalMap
内部类,数据都是存储在ThreadLocalMap
中。Thread
类里面有一个ThreadLocal.ThreadLocalMap
的变量threadLocals
。
每个线程的本地变量并不是存放在ThreadLocal
里面,而是存放在调用线程的threadLocals
变量里,也就是说ThreadLocal
类型的本地变量存放在具体的线程内存空间中, ThreadLocal
就是一个工具壳,它通过set
方法把value
值放入调用线程的threadLocals
里面并存放起来,当调用线程调用它的get
时,再从线程的threadLocals
里面将其拿出来使用。
ThreadLocalMap
,它是一种使用线性探测法实现的哈希表,底层使用数组存储数据,可以理解成就是一个HashMap
,这个哈希表的key
是ThreadLocal
,value
是ThreadLocal
存储的值。
使用线性探测法的哈希表使用数组存储元素,每次添加元素的时候,如果hash
到的位置已经有元素了,则向后移动一位检测是否有元素,如果还有元素,继续往后移直到一个没有元素的位置把当前要添加的元素放在那个位置。
比如,对于一个容量为8
的哈希表,我们依次放入2、10、3、4、11
这么几个元素:
①hash
值为2,2%8=2,所以放在下标为2的位置
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
哈希 | 2 |
②hash
值为10,10%8=2,所以放在下标为 2 的位置,但是下标2的位置有元素了,往后移一位,到下标为3的位置,下标为3没有元素,所以10放在了下标为3的位置;
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
哈希 | 2 | 10 |
③hash
值为3,3%8=3,所以放在下标为3的位置,但是下标3的位置有元素了,往后移一位,到下标为4的位置,没有元素,所以3放在了下标为4的位置;
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
哈希 | 2 | 10 | 3 |
④hash
值为4,4%8=4,所以放在下标为4的位置,但是下标4的位置有元素了,往后移一位,到下标为5的位置,没有元素,所以4放在了下标为5的位置;
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
哈希 | 2 | 10 | 3 | 4 |
⑤hash
值为11,11%8=3,所以放在下标为3的位置,但是下标3的位置有元素了,往后移一位,到下标为4的位置,也有元素了,继续后移,到下标为5的位置,依然有元素,继续后移,到下标为6的位置,没有元素,所以11放在了下标为6的位置
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
哈希 | 2 | 10 | 3 | 4 | 11 |
使用线性探测法实现的哈希表非常容易出现哈希冲突,且解决冲突的过程时间复杂度非常高,最高可以达到O(n)
的时间复杂度。
ThreadLocalMap
中的Entry
是一个弱引用,它引用的对象就是它的key
值,即ThreadLocal
变量,所以,当这个key
不具有强引用时,下一次垃圾回收时,这个弱引用本身会进入到引用队列中,等待被回收。不过我们一般把ThreadLocal
作为类的静态私有变量来使用。
缺点:
- 存储数据的
ThreadLocalMap
使用的是线性探测法,效率低下 - 使用结束未及时
remove()
掉ThreadLocal
中的值,容易造成内存泄漏,这种情况只能依靠下一次调用ThreadLocal
的时候来清除无用的数据,可以参考ThreadLocal
的set()/get()/remove()
中的相关代码
FastThreadLocal
学习FastThreadLocal
原理,还需要知道另外两个类:InternalThreadLocalMap
和 FastThreadLocalThread
。
InternalThreadLocalMap:对原生ThreadLocalMap
的优化,它不再使用线性探测法实现,查看FastThreadLocal
无参构造函数
public class FastThreadLocal<V> {
// 下标为0的被这个静态常量占用了
private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
private final int index;
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}
}
每个FastThreadLocal
创建时都会给它分配一个index
,使用这个index
定位FastThreadLocal
在数组中的精确位置,可以让时间复杂度降低到O (1)
,这个性能提升是很明显的。
比如,我们放入2、10、3、4、11
这么几个元素:每个元素创建的时候都给它分配一个index
,对于2、10、3、4、11
,它们的索引分别是1、2、3、4、5
,这样,它们在放入数组的时候直接按这个index
作为下标去数组对应的位置即可,也不会出现hash
冲突,当索引大小达到了数组长度扩容就好了。
这样使用也有个问题,当一个元素被回收的时候,它的index
并不会回收,也就是数组对应的位置不能重复利用,但是,这也不是一个问题,因为FastThreadLocal
一般作为类的静态属性来使用,正常情况下,是永远不会被回收的,所以,在FastThreadLocal
的使用场景下,这样的Map
完全没有问题。
InternalThreadLocalMap
内部使用数组存储FastThreadLocal
public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
// 数组默认值都为这个UNSET
public static final Object UNSET = new Object();
// 存储FastThreadLocal的数组
private Object[] indexedVariables;
private InternalThreadLocalMap() {
indexedVariables = newIndexedVariableTable();
}
private static Object[] newIndexedVariableTable() {
Object[] array = new Object[INDEXED_VARIABLE_TABLE_INITIAL_SIZE];
Arrays.fill(array, UNSET);
return array;
}
}
FastThreadLocalThread:对Thread
的包装,目的是为了配合FastThreadLocal
的使用。里面封装了一个InternalThreadLocalMap
字段,这样的话,以前使用Thread
的threadLocals
存储元素就变成了使用这个InternalThreadLocalMap
来存储元素。FastThreadLocalThread
中的构造方法还对任务进行了包装,使得线程任务运行结束的时候可以自动清理掉本线程相关的本地变量。
使用案例
public class FastThreadLocalTest {
private static FastThreadLocal<String> USER_THREAD_LOCAL = new FastThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
// thread1
new FastThreadLocalThread(() -> {
USER_THREAD_LOCAL.set("test001");
System.out.println("001:" + USER_THREAD_LOCAL.get());
}, "thread-001").start();
Thread.sleep(2000);
}
}
源码分析
FastThreadLocal
查看FastThreadLocal
构造函数
public class FastThreadLocal<V> {
// 下标为0的被这个静态常量占用了
private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
private final int index;
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}
}
InternalThreadLocalMap.nextVariableIndex()
public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
public static int nextVariableIndex() {
// nextIndex 是一个 AtomicInteger 类型
int index = nextIndex.getAndIncrement();
if (index < 0) {
nextIndex.decrementAndGet();
throw new IllegalStateException("too many thread-local indexed variables");
}
return index;
}
}
在FastThreadLocal
创建的时候给它分配了一个index
,这个index
是从一个AtomicInteger
中获取的,理论上来说,index
的值应该从0
开始,但是FastThreadLocal
中有一个常量字段variablesToRemoveIndex
,它会在FastThreadLocal
加载的时候就从 AtomicInteger
中拿走了第一个值0
,所以,实际上,FastThreadLocal
中的index
是从1
开始的。
接着看FastThreadLocalThread
构造函数
public class FastThreadLocalThread extends Thread {
public FastThreadLocalThread(Runnable target, String name) {
// FastThreadLocalRunnable.wrap(target)包装任务
// 然后调用父类
super(FastThreadLocalRunnable.wrap(target), name);
cleanupFastThreadLocals = true;
}
}
看看FastThreadLocalRunnable
是干啥的
final class FastThreadLocalRunnable implements Runnable {
// 原始任务
private final Runnable runnable;
private FastThreadLocalRunnable(Runnable runnable) {
this.runnable = ObjectUtil.checkNotNull(runnable, "runnable");
}
@Override
public void run() {
try {
// 1. 运行原始任务
runnable.run();
} finally {
// 2. run()结束后清理掉FastThreadLocal
FastThreadLocal.removeAll();
}
}
// 包装方法
static Runnable wrap(Runnable runnable) {
return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);
}
}
FastThreadLocal#set()
FastThreadLocal
的set
方法
public class FastThreadLocal<V> {
public final void set(V value) {
// 如果value不等于UNSET,就把它set进去
// 初始化时InternalThreadLocalMap中的数组的每个元素都会被初始化为UNSET
if (value != InternalThreadLocalMap.UNSET) {
// ①获取存储元素的InternalThreadLocalMap
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
// ②设置值
setKnownNotUnset(threadLocalMap, value);
} else {
remove();
}
}
}
InternalThreadLocalMap.get()
InternalThreadLocalMap.get()
public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
public static InternalThreadLocalMap get() {
Thread thread = Thread.currentThread();
// 判断当前线程是不是FastThreadLocalThread
// 这是为了兼容非FastThreadLocalThread也能使用FastThreadLocal
if (thread instanceof FastThreadLocalThread) {
return fastGet((FastThreadLocalThread) thread);
} else {
return slowGet();
}
}
private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
// 这个Map是存储在FastThreadLocalThread中的, 变量名为threadLocalMap
InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
if (threadLocalMap == null) {
// 初始化Map
thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
}
return threadLocalMap;
}
private static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap =
new ThreadLocal<InternalThreadLocalMap>();
private static InternalThreadLocalMap slowGet() {
// 使用Java原生的ThreadLocal来存储一个InternalThreadLocalMap
// 跟Netty相关的本地变量还是存储在InternalThreadLocalMap中
InternalThreadLocalMap ret = slowThreadLocalMap.get();
if (ret == null) {
ret = new InternalThreadLocalMap();
slowThreadLocalMap.set(ret);
}
return ret;
}
}
获取存储本地线程变量的InternalThreadLocalMap
分为两种情况:
- 如果当前线程是
FastThreadLocalThread
,则直接取FastThreadLocalThread
的threadLocalMap
属性 - 如果当前线程不是
FastThreadLocalThread
,则从FastThreadLocalThread
的slowThreadLocalMap
中获取,slowThreadLocalMap
是一个ThreadLocal
,在这个ThreadLocal
里面保存了一个线程本地变量InternalThreadLocalMap
setKnownNotUnset
拿到InternalThreadLocalMap
后,看看是怎么把值设置进去的。
public class FastThreadLocal<V> {
private final int index;
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}
private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
// 设置值到InternalThreadLocalMap中
if (threadLocalMap.setIndexedVariable(index, value)) {
// 将当前FastThreadLocal添加到待清理的Set中
addToVariablesToRemove(threadLocalMap, this);
}
}
}
threadLocalMap.setIndexedVariable(index, value)
设置值到InternalThreadLocalMap
中
public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
public boolean setIndexedVariable(int index, Object value) {
// 存储FastThreadLocal元素的数组
Object[] lookup = indexedVariables;
if (index < lookup.length) {
// 直接通过索引下标要存储的位置, 时间复杂度为O(1)
Object oldValue = lookup[index];
lookup[index] = value;
return oldValue == UNSET;
} else {
// 容量不够 需要先扩容再设置值
expandIndexedVariableTableAndSet(index, value);
return true;
}
}
private void expandIndexedVariableTableAndSet(int index, Object value) {
// 旧数组
Object[] oldArray = indexedVariables;
// 旧元素
final int oldCapacity = oldArray.length;
// 找到大于index的最小的2次方
// 比如,index=3,则为4
// index=16,则为32
// 如何实现取大于等于自己的最小2次方呢?只需要把这里改成(index-1)即可
int newCapacity = index;
newCapacity |= newCapacity >>> 1;
newCapacity |= newCapacity >>> 2;
newCapacity |= newCapacity >>> 4;
newCapacity |= newCapacity >>> 8;
newCapacity |= newCapacity >>> 16;
newCapacity ++;
// 创建新数组,并把旧数组的元素全部拷贝过来
Object[] newArray = Arrays.copyOf(oldArray, newCapacity);
// 把新数组不包含旧数组的后面部分都初始化为UNSET
Arrays.fill(newArray, oldCapacity, newArray.length, UNSET);
// 设置当前的值
newArray[index] = value;
// 把新数组赋值给InternalThreadLocalMap存储元素的数组
indexedVariables = newArray;
}
}
设置值分两种情况:
-
容量足够,直接把数组对应下标位置的值设置成新值即可,时间复杂度为O(1)
-
容量不够,先扩容,再设置新值
为什么使用index
作为基准来扩容,而不是直接扩容为旧数组容量的2倍?
比如默认这个数组的容量是32
,假设我们有100
个FastThreadLocal
变量,且它们都没有set()
过任何值,
此时,这个数组大小依然是32
,现在如果要设置第100
个FastThreadLocal
的值,根据前面的逻辑,我们知道,它的index
为100
,
那么此时,如果按旧数组的容量扩容为2
倍,依然无法承载index
为100
的这个元素,所以,需要按index
作为基准来进行扩容。
addToVariablesToRemove
此时数据已经设置到InternalThreadLocalMap
中,在上面,在添加成功之后,还有一步操作是把当前FastThreadLocal
添加到待清理的Set中
public class FastThreadLocal<V> {
private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
// variablesToRemoveIndex是FastThreadLocal中的常量,它的值为0
// 它对应的值同样存储在InternalThreadLocalMap中,是下标为0的元素
Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
// InternalThreadLocalMap里面的0号元素是一个Set
Set<FastThreadLocal<?>> variablesToRemove;
if (v == InternalThreadLocalMap.UNSET || v == null) {
// 初始化这个Set
variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
// 设置值到InternalThreadLocalMap中
threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
} else {
variablesToRemove = (Set<FastThreadLocal<?>>) v;
}
// 把当前FastThreadLocal添加到这个Set中
variablesToRemove.add(variable);
}
}
FastThreadLocalRunnable
final class FastThreadLocalRunnable implements Runnable {
@Override
public void run() {
try {
// 1. 运行原始任务
runnable.run();
} finally {
// 2. run()结束后清理掉FastThreadLocal
FastThreadLocal.removeAll();
}
}
}
Runnable
任务运行完毕后,里面会执行清理工作。
public class FastThreadLocal<V> {
private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
public static void removeAll() {
// 获取 InternalThreadLocalMap
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
if (threadLocalMap == null) {
return;
}
try {
// 取出待清理的FastThreadLocal
Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
if (v != null && v != InternalThreadLocalMap.UNSET) {
// 转换为Set
@SuppressWarnings("unchecked")
Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
// Set转数组
FastThreadLocal<?>[] variablesToRemoveArray =
variablesToRemove.toArray(new FastThreadLocal[0]);
// 遍历数组
for (FastThreadLocal<?> tlv: variablesToRemoveArray) {
// ①调用FastThreadLocal的remove()方法
tlv.remove(threadLocalMap);
}
}
} finally {
// ②调用InternalThreadLocalMap的remove()方法
InternalThreadLocalMap.remove();
}
}
}
这里主要做了两件事:
- 遍历当前线程中所有的
FastThreadLocal
并调用remove
方法 - 最后会调用
InternalThreadLocalMap
的remove()
方法
public class FastThreadLocal<V> {
public final void remove(InternalThreadLocalMap threadLocalMap) {
if (threadLocalMap == null) {
return;
}
// 从InternalThreadLocalMap中移除,这里是重置为UNSET元素
Object v = threadLocalMap.removeIndexedVariable(index);
// 从待清理的Set中移除
removeFromVariablesToRemove(threadLocalMap, this);
if (v != InternalThreadLocalMap.UNSET) {
try {
// 执行钩子方法, 是一个空方法
// Netty为我们预留的一个方法,我们可以继承自FastThreadLocal并实现onRemoval()方法
// 在移除FastThreadLocal的时候做一些我们自己的逻辑,比如清理我们自定义的资源等。
onRemoval((V) v);
} catch (Exception e) {
PlatformDependent.throwException(e);
}
}
}
}
public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
public static void remove() {
Thread thread = Thread.currentThread();
if (thread instanceof FastThreadLocalThread) {
// 从FastThreadLocalThread中移除InternalThreadLocalMap
((FastThreadLocalThread) thread).setThreadLocalMap(null);
} else {
// 从ThreadLocal中移除InternalThreadLocalMap本身
slowThreadLocalMap.remove();
}
}
}
总结
FastThreadLocal
正常来说是跟FastThreadLocalThread
联合使用的。但是,Netty 为了兼容性,也可以跟普通的Thread
一起使用,只是会使用一种slow
的方式来运行,这个slow
主要体现在InternalThreadLocalMap
的存储上,使用FastThreadLocalThread
时,它是存储在FastThreadLocalThread
中的,使用普通的Thread
时,它是存储在Java
原生的ThreadLocal
中的。FastThreadLocal
能帮助我们自动清理相关资源,这是通过包装Runnable
来实现的。
Netty版本
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.63.Final</version>
</dependency>