11package com .learnjava .concurent ;
22
33import java .util .concurrent .CountDownLatch ;
4+ import java .util .concurrent .ExecutorService ;
5+ import java .util .concurrent .Executors ;
6+ import java .util .concurrent .TimeUnit ;
47
58public 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}
0 commit comments