Skip to content

Commit 05c4bfd

Browse files
committed
更新ThreadLocal分析文章
1 parent 501f2a4 commit 05c4bfd

5 files changed

Lines changed: 863 additions & 381 deletions

File tree

JdkLearn/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@
3232
<artifactId>netty-all</artifactId>
3333
</dependency>
3434

35+
<!-- 线程池上下文传递 -->
36+
<dependency>
37+
<groupId>com.alibaba</groupId>
38+
<artifactId>transmittable-thread-local</artifactId>
39+
<version>2.14.5</version>
40+
</dependency>
41+
3542
<!-- redis依赖 -->
3643
<dependency>
3744
<groupId>org.springframework.boot</groupId>

JdkLearn/src/main/java/com/learnjava/concurent/ThreadLocalTest.java

Lines changed: 139 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
package com.learnjava.concurent;
22

33
import java.util.concurrent.CountDownLatch;
4+
import java.util.concurrent.ExecutorService;
5+
import java.util.concurrent.Executors;
6+
import java.util.concurrent.TimeUnit;
47

58
public class ThreadLocalTest {
69
public static void main(String[] args) throws InterruptedException {
10+
// testThreadIsolation();
11+
// testInitialValue();
12+
// testSetGetRemove();
13+
// testThreadLocalAsMapKey();
14+
// testThreadPoolShouldRemove();
15+
testHashIncrementDistribution();
16+
}
17+
18+
/**
19+
* 核心结果:同一个ThreadLocal对象,在不同线程中保存的是不同副本。
20+
*/
21+
public static void testThreadIsolation() throws InterruptedException {
22+
System.out.println("\n==== 1. thread isolation ====");
723
int threads = 3;
824
CountDownLatch countDownLatch = new CountDownLatch(threads);
925

@@ -20,7 +36,129 @@ public static void main(String[] args) throws InterruptedException {
2036
}, "thread - " + i).start();
2137
}
2238
countDownLatch.await();
39+
}
40+
41+
/**
42+
* initialValue只会在当前线程第一次get且没有值时触发。
43+
*/
44+
public static void testInitialValue() throws InterruptedException {
45+
System.out.println("\n==== 2. initialValue per thread ====");
46+
ThreadLocal<StringBuilder> local = new ThreadLocal<StringBuilder>() {
47+
@Override
48+
protected StringBuilder initialValue() {
49+
System.out.printf("%s init value\n", Thread.currentThread().getName());
50+
return new StringBuilder(Thread.currentThread().getName());
51+
}
52+
};
53+
54+
Runnable task = () -> {
55+
System.out.printf("%s first get: %s\n", Thread.currentThread().getName(), local.get());
56+
System.out.printf("%s second get: %s\n", Thread.currentThread().getName(), local.get());
57+
};
58+
59+
Thread threadA = new Thread(task, "thread-A");
60+
Thread threadB = new Thread(task, "thread-B");
61+
threadA.start();
62+
threadB.start();
63+
threadA.join();
64+
threadB.join();
65+
}
66+
67+
/**
68+
* set/get/remove是ThreadLocal最常用的生命周期。
69+
*/
70+
public static void testSetGetRemove() {
71+
System.out.println("\n==== 3. set get remove ====");
72+
ThreadLocal<String> local = new ThreadLocal<String>() {
73+
@Override
74+
protected String initialValue() {
75+
return "init";
76+
}
77+
};
78+
79+
System.out.println("first get: " + local.get());
80+
local.set("changed");
81+
System.out.println("after set: " + local.get());
82+
local.remove();
83+
System.out.println("after remove, get again: " + local.get());
84+
}
85+
86+
/**
87+
* 原理要点:值不是存在ThreadLocal对象里,而是存在当前线程的ThreadLocalMap里。
88+
* ThreadLocal实例本身作为key,所以同一个线程可以给不同ThreadLocal保存不同值。
89+
*/
90+
public static void testThreadLocalAsMapKey() {
91+
System.out.println("\n==== 4. ThreadLocal instance as key ====");
92+
ThreadLocal<String> userLocal = new ThreadLocal<String>();
93+
ThreadLocal<String> traceLocal = new ThreadLocal<String>();
94+
95+
userLocal.set("user-1001");
96+
traceLocal.set("trace-abc");
97+
98+
System.out.printf("same thread:%s, userLocal:%s, traceLocal:%s\n",
99+
Thread.currentThread().getName(),
100+
userLocal.get(),
101+
traceLocal.get());
23102

103+
userLocal.remove();
104+
traceLocal.remove();
105+
}
106+
107+
/**
108+
* 线程池会复用线程;如果任务结束后不remove,后续任务可能读到上一个任务遗留的值。
109+
*/
110+
public static void testThreadPoolShouldRemove() throws InterruptedException {
111+
System.out.println("\n==== 5. thread pool should remove ====");
112+
ThreadLocal<String> requestIdLocal = new ThreadLocal<String>();
113+
ExecutorService executorService = Executors.newSingleThreadExecutor();
114+
115+
executorService.execute(() -> {
116+
requestIdLocal.set("request-1");
117+
System.out.printf("%s set value: %s\n",
118+
Thread.currentThread().getName(),
119+
requestIdLocal.get());
120+
});
121+
122+
executorService.execute(() -> {
123+
System.out.printf("%s read old value: %s\n",
124+
Thread.currentThread().getName(),
125+
requestIdLocal.get());
126+
requestIdLocal.remove();
127+
});
128+
129+
executorService.execute(() -> System.out.printf("%s after remove: %s\n",
130+
Thread.currentThread().getName(),
131+
requestIdLocal.get()));
132+
133+
executorService.shutdown();
134+
executorService.awaitTermination(3, TimeUnit.SECONDS);
135+
136+
}
137+
138+
/**
139+
* 验证ThreadLocal中0x61c88647这个hash增量的分布效果。
140+
* ThreadLocalMap长度是2的幂,定位下标时使用 hash & (len - 1)。
141+
*/
142+
public static void testHashIncrementDistribution() {
143+
System.out.println("\n==== 6. hash increment distribution ====");
144+
int hashIncrement = 0x61c88647;
145+
146+
printHashIndexSequence(hashIncrement, 16);
147+
printHashIndexSequence(hashIncrement, 32);
148+
}
149+
150+
private static void printHashIndexSequence(int hashIncrement, int len) {
151+
int hash = 0;
152+
153+
System.out.printf("len = %d, HASH_INCREMENT & (len - 1) = %d\n",
154+
len,
155+
hashIncrement & (len - 1));
156+
157+
for (int i = 0; i < len; i++) {
158+
int index = hash & (len - 1);
159+
System.out.printf("%2d -> hash: 0x%08x, index: %2d\n", i, hash, index);
160+
hash += hashIncrement;
161+
}
24162
}
25163

26164
private static class InnerClass {
@@ -54,11 +192,6 @@ public void set(String word) {
54192
}
55193

56194
private static class Counter {
57-
private static ThreadLocal<StringBuilder> counter = new ThreadLocal<StringBuilder>() {
58-
@Override
59-
protected StringBuilder initialValue() {
60-
return new StringBuilder();
61-
}
62-
};
195+
private static ThreadLocal<StringBuilder> counter = ThreadLocal.withInitial(StringBuilder::new);
63196
}
64197
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.learnjava.concurent;
2+
3+
import com.alibaba.ttl.TransmittableThreadLocal;
4+
import com.alibaba.ttl.threadpool.TtlExecutors;
5+
6+
import java.util.concurrent.ExecutorService;
7+
import java.util.concurrent.Executors;
8+
import java.util.concurrent.TimeUnit;
9+
10+
/**
11+
* 演示 TransmittableThreadLocal 在线程池中的上下文传递。
12+
*/
13+
public class TransmittableThreadLocalDemo {
14+
15+
private static final ThreadLocal<String> NORMAL_THREAD_LOCAL = new ThreadLocal<>();
16+
private static final InheritableThreadLocal<String> INHERITABLE_THREAD_LOCAL = new InheritableThreadLocal<>();
17+
private static final TransmittableThreadLocal<String> TTL = new TransmittableThreadLocal<>();
18+
19+
public static void main(String[] args) throws InterruptedException {
20+
testThreadLocalInThreadPool();
21+
testInheritableThreadLocalInThreadPool();
22+
testTransmittableThreadLocalInThreadPool();
23+
}
24+
25+
/**
26+
* 普通 ThreadLocal 不能跨线程传递值。
27+
*/
28+
private static void testThreadLocalInThreadPool() throws InterruptedException {
29+
System.out.println("\n==== 1. normal ThreadLocal ====");
30+
ExecutorService executorService = Executors.newFixedThreadPool(1);
31+
32+
NORMAL_THREAD_LOCAL.set("normal-context");
33+
executorService.execute(() -> printValue("normal", NORMAL_THREAD_LOCAL.get()));
34+
35+
shutdown(executorService);
36+
NORMAL_THREAD_LOCAL.remove();
37+
}
38+
39+
/**
40+
* InheritableThreadLocal 只在线程创建时传递,线程池复用线程时容易读到旧值。
41+
*/
42+
private static void testInheritableThreadLocalInThreadPool() throws InterruptedException {
43+
System.out.println("\n==== 2. InheritableThreadLocal ====");
44+
ExecutorService executorService = Executors.newFixedThreadPool(1);
45+
46+
INHERITABLE_THREAD_LOCAL.set("parent-context-1");
47+
executorService.execute(() -> printValue("first task", INHERITABLE_THREAD_LOCAL.get()));
48+
sleepQuietly(200);
49+
50+
INHERITABLE_THREAD_LOCAL.set("parent-context-2");
51+
executorService.execute(() -> printValue("second task", INHERITABLE_THREAD_LOCAL.get()));
52+
53+
shutdown(executorService);
54+
INHERITABLE_THREAD_LOCAL.remove();
55+
}
56+
57+
/**
58+
* TransmittableThreadLocal 通过包装线程池,在任务提交时捕获上下文,在执行时恢复上下文。
59+
*/
60+
private static void testTransmittableThreadLocalInThreadPool() throws InterruptedException {
61+
System.out.println("\n==== 3. TransmittableThreadLocal ====");
62+
ExecutorService rawExecutorService = Executors.newFixedThreadPool(1);
63+
ExecutorService ttlExecutorService = TtlExecutors.getTtlExecutorService(rawExecutorService);
64+
65+
TTL.set("ttl-context-1");
66+
ttlExecutorService.execute(() -> printValue("first ttl task", TTL.get()));
67+
68+
TTL.set("ttl-context-2");
69+
ttlExecutorService.execute(() -> printValue("second ttl task", TTL.get()));
70+
71+
shutdown(ttlExecutorService);
72+
TTL.remove();
73+
}
74+
75+
private static void printValue(String scene, String value) {
76+
System.out.printf("%s, thread=%s, value=%s%n", scene, Thread.currentThread().getName(), value);
77+
}
78+
79+
private static void shutdown(ExecutorService executorService) throws InterruptedException {
80+
executorService.shutdown();
81+
executorService.awaitTermination(3, TimeUnit.SECONDS);
82+
}
83+
84+
private static void sleepQuietly(long millis) {
85+
try {
86+
TimeUnit.MILLISECONDS.sleep(millis);
87+
} catch (InterruptedException e) {
88+
Thread.currentThread().interrupt();
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)