Threadlocal源码一些分析
程序员文章站
2022-07-14 18:05:45
...
Threadlocal思想:每个线程里面有一个map,map的键存一个Threadlocal变量,map的值存该Threadlocal所要保存的的该线程下的线程隔离对象。所以对象副本其实是存在于各线程内部,而Threadlocal只是一个工具来完成这一切。
先看类图结构:
Threadlocal
Threadlocal是裸的,除了Object没有任何父类,没有实现任何接口。
1.成员变量
// 原子类int自增类
private static AtomicInteger nextHashCode = new AtomicInteger();
// hash增量,一个常量而已
private static final int HASH_INCREMENT = 0x61c88647;
//
private final int threadLocalHashCode = nextHashCode();
2.方法暂不罗列
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
Threadlocal内部类ThreadLocalMap
1.成员变量
// 初始容量,必须是2的指数倍(和HashMap要求一样)
private static final int INITIAL_CAPACITY = 16;
// Entry数组
private Entry[] table;
// Threadlocal里面的Entry数量
private int size = 0;
2.方法
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value)
ThreadLocalMap内部类Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Thread
1.成员变量
// Thread类有一个ThreadLocal.ThreadLocalMap类型的成员变量
ThreadLocal.ThreadLocalMap threadLocals = null;
正片开始
一、线程第一次set元素
- Test.java (测试类)
// 新建类变量threadLocal
public static ThreadLocal threadLocal= new ThreadLocal(); < — — — — — a.进去
- ThreadLocal
// 唯一的构造方法
public ThreadLocal() {
}
- Test.java (测试类)
// 给threadLocal添加一个元素进去
threadLocal.set(10); < — — — — — b.进去
- ThreadLocal
public void set(T value) {
// 得到当前线程
Thread t = Thread.currentThread();
// 调用getMap()方法,传入当前线程对象
ThreadLocalMap map = getMap(t); < — — — — — c.进去
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
- ThreadLocal
// 参数:t就是当前线程
ThreadLocalMap getMap(Thread t) {
// 返回当前线程的threadLocals成员对象
return t.threadLocals; < — — — — — d.返回
}
- ThreadLocal
public void set(T value) {
Thread t = Thread.currentThread();
// 得到了当前线程的threadLocals,这就是装东西的map
// 把前线程的threadLocals成员对象,赋给map
ThreadLocalMap map = getMap(t);
// map目前是null,进入else
if (map != null)
map.set(this, value);
else
// 调用create方法
createMap(t, value); < — — — — — e.进入
}
- ThreadLocal
// 参数:t就是当前线程,firstValue是我们传入的对象
void createMap(Thread t, T firstValue) {
// 新建一个ThreadLocalMap对象
// 把ThreadLocalMap对象赋给当前线程的threadLocals成员变量
t.threadLocals = new ThreadLocalMap(this, firstValue); < — — — — — f.进入
}
- ThreadLocal.ThreadLocalMap
// ThreadLocalMap构造方法
// 传入参数:firstKey就是当前ThreadLocal对象,firstValue就是我们传入的对象
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 新建Entry数组,长度为16
table = new Entry[INITIAL_CAPACITY];
// 计算一个threadLocalHashCode
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); < — — — — — g.进入
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
- ThreadLocal
// 以下几步主要是最终通过AtomicInteger为该ThreadLocal对象生成一个hash值
// ⚠️注意final修饰,
// 调用nextHashCode()来计算这个值
private final int threadLocalHashCode = nextHashCode(); < — — — — — h.进入
- ThreadLocal
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
// 调用nextHashCode的getAndAdd()方法
// nextHashCode是一个AtomicInteger类的类成员变量
return nextHashCode.getAndAdd(HASH_INCREMENT); < — — — — — i.进入
}
- AtomicInteger
// 该方法是AtomicInteger类的原子性自增方法
// AtomicInteger通过CAS实现原子性操作,是通过硬件实现的
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5; < — — — — — j.一路返回
}
- ThreadLocal.ThreadLocalMap
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
// threadLocalHashCode 按位与 1111,其实就是取模运算,得到桶的index
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 在i桶处新建Entry
// firstKey就是当前ThreadLocal对象
// firstValue我们传入的对象
table[i] = new Entry(firstKey, firstValue);
// ThreadLocal的size=1
size = 1;
// 设置阈值
setThreshold(INITIAL_CAPACITY);
}
二、线程第二次set元素
- ThreadLocal
// 再次设置一个元素进去
threadLocal.set(20); < — — — — — a.进入
- ThreadLocal
// 参数value就是我们传入的值
public void set(T value) {
// 得到当前线程
Thread t = Thread.currentThread();
// 根据当前线程得到ThreadLocalMap
ThreadLocalMap map = getMap(t);
// map不为空,进入if
if (map != null)
map.set(this, value); < — — — — — b.进入
else
createMap(t, value);
}
- ThreadLocal.ThreadLocalMap
// 参数key就是当前ThreadLocal对象,value就是我们传入值
private void set(ThreadLocal<?> key, Object value) {
// 得到该线程的ThreadLocalMap里面的数组
Entry[] tab = table;
// len = 16
int len = tab.length;
// 得到当前ThreadLocal的hash值,取模运算,得到桶index
int i = key.threadLocalHashCode & (len-1);
// 遍历所有桶
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 一旦找到Entry的key和传入的key一样的Entry
if (k == key) {
// 替换其旧值,返回
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 如果遍历了链表都未找到,证明该线程的ThreadLocalMap还没有添加过该ThreadLocal
// 新建一个Entry,key就是该ThreadLocal,value是我们要set的值
// 放入第i桶
tab[i] = new Entry(key, value);
// 体积+1
int sz = ++size;
// 看是否需要扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold) < — — — — — c.进入
rehash();
}
- ThreadLocal
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
// 循环遍历所有桶
do {
i = nextIndex(i, len);
Entry e = tab[i];
// 如果该桶不是null,但是该桶的内容是null
if (e != null && e.get() == null) {
n = len;
// 设置移除标志为true
removed = true;
i = expungeStaleEntry(i); //太复杂不分析了
}
// 只要n/2不等于0,就循环
// n = n/2
} while ( (n >>>= 1) != 0);
return removed;
}
可以看出,ThreadLocalMap里面的map和HashMap是不一样的,ThreadLocalMap里面只有数组,没有链表。
如果使用线程池,那么使用了Threadlocal后需要清理掉remove,不然map还存在于thread对象中,下次就有被拿来用了。
Threadlocal里面还有个强/弱引用的问题和内存清理的问题。
⚠️⚠️spring:一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程。
同一线程贯通三层这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。
上一篇: STM32延时功能
下一篇: leetcode 对称二叉树
推荐阅读
-
PHP源码分析之变量的存储过程分解_PHP教程
-
promise原理与源码分析
-
Spring源码解析之-TypeConverter、TypeConverterDelegate分析
-
Netty源码分析 (十一)----- 拆包器之LengthFieldBasedFrameDecoder
-
django源码分析:信号signal
-
Flutter之通过AnimationController源码分析学习使用Animation
-
yii框架源码分析之创建controller代码_PHP
-
MetaQ技术内幕——源码分析(三)
-
源码分析 Laravel 重复执行同一个队列任务的原因
-
Redis源码分析(十三)---redis-benchmark性能测试