目录
  1. 1. ThreadLocal源码解析
    1. 1.1. 类定义
    2. 1.2. 主要变量
    3. 1.3. 内部类
      1. 1.3.1. SuppliedThreadLocal
      2. 1.3.2. ThreadLocalMap
    4. 1.4. 构造方法
    5. 1.5. 主要方法
      1. 1.5.1. get
      2. 1.5.2. set
      3. 1.5.3. remove
      4. 1.5.4. createInheritedMap
      5. 1.5.5. childValue
ThreadLocal源码解析

ThreadLocal源码解析

类定义

  此类提供线程局部变量.这些变量与普通变量不同,每个访问一个线程(通过其get或set}方法)的线程都有其自己的变量,独立初始化的变量副本. ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段(例如:用户ID或交易ID)

例如:下面的类生成每个线程本地的唯一标识符.线程的ID是在首次调用 ThreadId.get()时分配的,并且在以后的调用中保持不变.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.concurrent.atomic.AtomicInteger;

public class ThreadId {
// 包含要分配的下一个线程ID的原子整数
private static final AtomicInteger nextId = new AtomicInteger(0);

// 包含每个线程ID的线程局部变量
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};

// 返回当前线程的唯一ID,并在必要时分配它
public static int get() {
return threadId.get();
}
}

只要线程是活动的并且可以访问ThreadLocal实例,每个线程都会对其线程局部变量的副本保留隐式引用.线程消失后,其线程本地实例的所有副本都将进行垃圾回收(除非存在对这些副本的其他引用)

主要变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* ThreadLocals依赖于附加到每个线程(Thread.threadLocals和InheritableThreadLocals)的每线程线性探针哈希映射.
* ThreadLocal对象充当键,通过threadLocalHashCode搜索.这是一个自定义哈希码(仅在ThreadLocalMaps中有用).
* 在相同的线程使用连续构造的ThreadLocals的常见情况下,它消除了冲突,而在不太常见的情况下,它们表现良好
*/
private final int threadLocalHashCode = nextHashCode();

/**
* 下一个要给出的哈希码.原子更新,开始于0
*/
private static AtomicInteger nextHashCode = new AtomicInteger();

/**
* 连续生成的哈希码之间的差异-将隐式顺序线程本地ID转换为用于2的幂次方表的近似最佳分布的乘法哈希值
*/
private static final int HASH_INCREMENT = 0x61c88647;

内部类

SuppliedThreadLocal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* ThreadLocal的扩展:从指定的Supplier获取其初始值
*/
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

private final Supplier<? extends T> supplier;

SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}

@Override
protected T initialValue() {
return supplier.get();
}
}

ThreadLocalMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
/**
* ThreadLocalMap是自定义的哈希映射,仅适用于维护线程局部值.没有操作导出到ThreadLocal类之外
* 该类是包私有的,以允许声明Thread类中的字段.
* 为了帮助处理非常大且长期存在的用法,哈希表entry使用WeakReferences作为键.但是由于未使用参考队列,因此仅在表开始空间不足时,才保证删除过时的条目
*/
static class ThreadLocalMap {

/**
* 此哈希映射中的entry使用其主要引用字段作为键(始终是ThreadLocal对象)扩展WeakReference
* 请注意:空键(即entry.get() == null)意味着不再引用该键,因此可以从表中删除该条目.在下面的代码中,此类条目称为"陈旧条目"
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** 与此ThreadLocal关联的值 */
Object value;

// key就是ThreadLocal对象本身,而值就是大家想要保存的数据如数据库连接
Entry(ThreadLocal<?> k, Object v) {
// 将k置为弱引用
super(k);
value = v;
}
}

/**
* 初始容量-必须是2的幂
*/
private static final int INITIAL_CAPACITY = 16;

/**
* 该表根据需要调整大小.
* table.length必须始终为2的幂
*/
private Entry[] table;

/**
* 表中的实体数
*/
private int size = 0;

/**
* 要调整大小的下一个大小值
* 默认为0
*/
private int threshold;

/**
* 设置调整大小阈值以保持最坏的2/3负载系数
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}

/**
* 下一个索引
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}

/**
* 上一个索引
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}

/**
* 构造一个最初包含(firstKey, firstValue)的新Map
* ThreadLocalMaps是延迟构造的,因此只有在至少要放置一个entry时才创建一个
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}

/**
* 构造一个新Map,其中包括给定父Map中的所有可继承ThreadLocals.仅由createInheritedMap调用
* @param parentMap 与父线程关联的Map
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];

for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}

/**
* 根据key获取对应的entry
* 此方法本身只处理快速路径:直接命中存在key;否则它将中继到getEntryAfterMiss.旨在通过部分使此方法易于操作来最大程度地提高直接打击的性能
* @param key 线程本地对象
* @return 与key对应的entry(如果不存在则返回null)
*/
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}

/**
* 当在其直接哈希槽中找不到密钥时使用的getEntry方法的版本
* @param key 线程本地对象
* @param i 密钥的哈希表的索引
* @param e 哈希表的entry
* @return 与key对应的entry(如果不存在则返回null)
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;

while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}

/**
* 设置与key对应的值
* @param key 线程本地对象
* @param value 要设置的值
*/
private void set(ThreadLocal<?> key, Object value) {

// 我们不像get()那样使用快速路径
// 因为使用set()创建新entry和替换现有条目至少是一样普遍,在这种情况下,快速路径失败的可能性会更大

Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);

for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();

if (k == key) {
e.value = value;
return;
}

if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}

tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

/**
* 移除key对应的entry
*/
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}

/**
* 替换指定key的entry
* 无论是否已存在指定key的entry,在value参数中传递的值都存储在entry中
*
* 副作用: 此方法删除了包含过时entry的"运行"中的所有过时entry(运行是两个空槽之间的一系列输入.)
* @param key 键值
* @param value key对应的value
* @param 搜索key时遇到的第一个陈旧entry的staleSlot索引
*/
private void replaceStaleEntry(ThreadLocal<?> key, Object value,int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;

// 备份以检查当前运行中是否有过时的entry.我们一次清理整个运行,以避免由于垃圾收集器释放成堆的引用(即每当收集器运行时)而导致的连续增量重新哈希
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;

// 查找运行的键或尾随空槽,以先到者为准
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();

// 如果找到key则需要将其与旧entry交换以维护哈希表的顺序.然后可以将新旧插槽或在其上方遇到的任何其他旧插槽发送到expungeStaleEntry,以删除或重新哈希运行中的所有其他条目
if (k == key) {
e.value = value;

tab[i] = tab[staleSlot];
tab[staleSlot] = e;

// 如果先前的陈旧entry存在则开始清除
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}

// 如果在向后扫描中未找到过时的entry,则在扫描key时看到的第一个过时的entry仍然是运行中的第一个过时的entry
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}

// 如果找不到key,则将新entry放入陈旧的插槽中
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);

// 如果有其他过时的entry正在运行,请将它们清除
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}

/**
* 通过对位于Staleslot和下一个空时隙之间的任何可能冲突的entry进行重新编码来消除陈旧的entry.这也消除了尾随空值之前遇到的任何其他陈旧entry
* @param staleSlot 已知具有空键的插槽索引
* @return staleSlot之后的下一个空槽的索引(将检查staleSlot和该插槽之间的所有内存)
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;

// 在staleSlot删除条目
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;

// 重新哈希直到遇到null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;

// 我们必须扫描到null为止,因为可能已经过时了多个条目
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}

/**
* 启发式扫描某些单元以查找陈旧entry.
* 当添加了新元素或已删除另一旧元素时,将调用此方法.
* 它执行对数扫描,作为无扫描(快速但保留垃圾)和与元素数量成比例的扫描数量之间的平衡,这会发现所有垃圾,但会导致某些插入花费O(n)时间
*
* @param i 一个不持有陈旧entry的位置.扫描从i之后的元素开始
* @param n 扫描控制:除非找到过时的entry,否则将扫描log2(n)个单元格,在这种情况下,将扫描另外的log2(table.length)-1个单元格.从插入处调用时,此参数是元素数,但是从replaceStaleEntry中调用时,它是表长度.(注意:可以通过对n加权而不是仅使用直接对数n来将所有这些更改为或多或少具有攻击性.但是此版本简单、快速、并且似乎运行良好)
* @return 如果已删除任何过时的条目则为true。
*/
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];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}

/**
* 重新包装和/或调整table大小.
* 首先扫描整个表,删除陈旧的entry;如果这还不足以缩小表格的大小,请将表格大小加倍
*/
private void rehash() {
expungeStaleEntries();

// 使用较低的阈值加倍以避免滞后
if (size >= threshold - threshold / 4)
resize();
}

/**
* 将表的容量加倍
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;

for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // 有利于GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}

setThreshold(newLen);
size = count;
table = newTab;
}

/**
* 清除表中所有过时的entry
*/
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
}

构造方法

1
2
3
4
/**
* 创建线程局部变量
*/
public ThreadLocal() {}

主要方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 返回下一个哈希码
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}

/**
* 为这个线程局部变量返回当前线程的"初始值".
* 当线程第一次使用GET方法访问变量时,将调用此方法
* 除非该线程先前调用了set方法,在这种情况下,将不会为该线程调用initialValue方法.
* 通常,每个线程最多只调用一次此方法,但在随后调用Remove之后再调用GET时,可以再次调用该方法
*
* 该实现仅返回null;如果程序员希望线程局部变量的初始值不是null,则必须将ThreadLocal子类化,并重写此方法.通常,将使用匿名内部类
*/
protected T initialValue() {
return null;
}

/**
* 创建线程局部变量.变量的初始值是通过调用Supplier上的get方法确定的
*
* @param <S> 线程局部值的类型
* @param supplier 用于确定初始值的supplier
* @return 一个新的线程局部变量
* @throws 当如果指定的supplier为空时,抛出NullPointerException
*/
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}

get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* 返回此线程局部变量的当前线程副本中的值
* 如果变量没有当前线程的值,则首先将其初始化为调用initialValue方法返回的值
*/
public T get() {
Thread t = Thread.currentThread();
// 获取当前线程的 threadLocals 属性
ThreadLocalMap map = getMap(t);
if (map != null) {
// 若threadLocals属性不为空,以this(当前ThreadLocal)实例为健获取对应的值entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 若已经设置过值或者有初始值就直接返回
T result = (T)e.value;
return result;
}
}
// 当前线程的threadLocals属性为空或者没有设置过值时设置初始值
return setInitialValue();
}

/**
* 获取与给定线程相关联的ThreadLoal散列表
* @param t 当前线程
* @return 返回对应的ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

/**
* set方法的变体以建立initialValue.
* 如果用户已覆盖set()方法,请使用它代替set()
* @return 初始值
*/
private T setInitialValue() {
// 调用 initialValue 获取初始值默认为 null
T value = initialValue();
Thread t = Thread.currentThread();
// 获取当前线程的 threadLocals 属性
ThreadLocalMap map = getMap(t);
if (map != null)
// 如果已经创建与当前线程关联的 ThreadLoal 散列表,则直接设值
map.set(this, value);
else
// 创建与当前线程相关的 ThreadLocal 散列表 并设值
createMap(t, value);
return value;
}

/**
* 创建与当前线程关联的ThreadLocal散列表.并将它赋值给给定线程的threadLocals属性
* @param t 当前线程
* @param ThreadLocal散列表第一个Entry的初始值
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 向当前线程的线程私有变量设置指定的值
* 大多数子类将不需要重写这个方法,仅仅依靠initialValue方法来设置线程局部变量的值
*/
public void set(T value) {
Thread t = Thread.currentThread();
// 获取当前线程的 threadLocals 属性
ThreadLocalMap map = getMap(t);
if (map != null)
// 如果已经创建与当前线程关联的ThreadLoal散列表,则直接设值
map.set(this, value);
else
// 创建与当前线程相关的ThreadLocal散列表,并设值
createMap(t, value);
}

remove

1
2
3
4
5
6
7
8
9
10
/**
* 移除此线程局部变量的当前线程值
* 如果此线程局部变量随后由当前线程GET读取,则其值将通过调用其initialValue方法重新初始化,除非其值是当前线程在此期间设置的SET
* 这可能导致当前线程中的initialValue方法多次调用
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}

createInheritedMap

1
2
3
4
5
6
7
8
/**
* 创建继承的线程局部变量映射的工厂方法.设计为仅从Thread构造函数调用
* @param parentMap 与父线程关联的map
* @return 包含父级可继承绑定的映射
*/
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}

childValue

1
2
3
4
5
6
7
8
/**
* 方法childValue在InheritableThreadLocal子类中可见地定义
* 但是在此处内部定义是为了提供createInheritedMap工厂方法而无需在InheritableThreadLocal中子类化Map类
* 此技术优于在方法中嵌入测试实例的替代方法
*/
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
文章作者: Eric Liang
文章链接: https://ericql.github.io/2019/11/12/01-Java%E5%9F%BA%E7%A1%80%E7%AF%87/02-JDK%E6%BA%90%E7%A0%81%E7%AF%87/ThreadLocal%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Eric Liang
打赏
  • 微信
  • 支付宝