【Java】深入理解ThreadLocal

一、前言

要理解ThreadLocal,首先必须理解线程安全。线程可以看做是一个具有一定独立功能的处理过程,它是比进程更细度的单位。当程序以单线程运行的时候,我们不需要考虑线程安全。然而当一个进程中包含多个线程的时候,就需要考虑线程安全问题,因为此时线程可能会同时操作同一个资源,当两个或者两个以上线程同时操作一个资源的时候,就会造成冲突、不一致等问题,即线程不安全。

解决线程安全问题,本质上就是解决资源共享问题,一般有以下手段:

1)可重入(不依赖环境);2)互斥(同一时间段只允许一个线程使用);3)原子操作;4)Thread-Local

二、Thread-Local

Thread-Local是一个很简单的思想:如果一个资源会引起线程竞争,那就为每一个线程配备一个资源。这就是ThreadLocal需要做的事情。

三、ThreadLocal的用法

在理解ThreadLocal之前,首先看一下它的用法:

 1 public class ThreadLocalTest {
2 public static ThreadLocal<Integer> intLocal = new ThreadLocal<Integer>() {
3 @Override
4 protected Integer initialValue() {
5 // TODO Auto-generated method stub
6 return 0;
7 }
8
9 @Override
10 public Integer get() {
11 // TODO Auto-generated method stub
12 set(super.get() + 1);
13 return super.get();
14 }
15
16 @Override
17 public void set(Integer value) {
18 // TODO Auto-generated method stub
19 super.set(value);
20 }
21
22 @Override
23 public void remove() {
24 // TODO Auto-generated method stub
25 super.remove();
26 }
27 };
28
29 public static void main(String[] args) {
30 // TODO Auto-generated method stub
31 for(int index = 0; index < 3; index ++)
32 new MyThread(index).start();
33 }
34 }
35
36 class MyThread extends Thread{
37 int id;
38
39 public MyThread(int id){
40 this.id = id;
41 }
42
43 @Override
44 public void run() {
45 // TODO Auto-generated method stub
46 for(int index = 0; index < 3; index ++){
47 System.out.println("Thread-" + id + " : " + ThreadLocalTest.intLocal.get());
48 try {
49 Thread.sleep((int)(100 * Math.random()));
50 } catch (InterruptedException e) {
51 // TODO Auto-generated catch block
52 e.printStackTrace();
53 }
54 }
55 }
56 }

其打印结果如下:

1 Thread-1 : 1
2 Thread-0 : 1
3 Thread-2 : 1
4 Thread-0 : 2
5 Thread-2 : 2
6 Thread-1 : 2
7 Thread-2 : 3
8 Thread-0 : 3
9 Thread-1 : 3

这是一个很典型的问题,在学习多线程以及同步的时候,几乎所有的书本都会使用类似的一个例子:银行存钱取钱问题。我们知道当多线程并发操作一个int值的加减操作的时候,最后的数值会产生很大的不确定性,得不到最终正确的结果。

而从例子中我们可以看到:每一个线程对int值的操作都是独立的,我们使用的只是同一个静态的intLocal类型!通过使用TreadLocal,我们可以为每一个线程提供独立的资源副本,从而完成对资源的“共享”操作。

ThreadLocal类中可重载的方法只有四个:

1)set():设置值,也就是说,我们选择将某个值设置为ThreadLocal类型的;

2)get():将设置进去的值取出来;

3)remove():我们不想将某个值设置为ThreadLocal了,移除掉;

4)initialValue():如果get的时候还没有设置值,就使用这个方法进行初始化;

使用过程简单明了,一般重载initialValue()提供一个初始值就可以了,其余方法不需要重载。

四、ThreadLocal的实现

看源码是最直接也是最有效的学习方式,不但可以掌握其原理,也可以学习Java源码精巧的实现方式。

ThreadLocal的实现代码略长,我们选取重要的方法作为切入点:


 1     public T get() {
2 Thread t = Thread.currentThread();
3 ThreadLocalMap map = getMap(t);
4 if (map != null) {
5 ThreadLocalMap.Entry e = map.getEntry(this);
6 if (e != null)
7 return (T)e.value;
8 }
9 return setInitialValue();
10 }

第一个方法是get()方法。这边出现了Thread和Map,联想到Thread的作用,大致应该可以猜到ThreadLocal的实现:维护一个Map,以Thread作为键,以变量作为值,每一次要使用变量的时候,就以当前的Thread作为键去取值,如果没有,就初始化一个值返回,如果有,就直接返回。

事实上,ThreadLocal的实现思路的确大致如此。但是我们要做的事情其实更多:

A.如果要设置更多的值怎么办?也就是说,我们有多种资源需要共享怎么办?

   
 B.为每一个线程共享一个资源,如何回收?

我们言归正传,从源码中获取答案。

get()方法的第3行中出现了一个ThreadLocalMap实例,它是从getMap()方法获取的,其方法如下:

1 ThreadLocalMap getMap(Thread t) {
2 return t.threadLocals;
3 }

t表示当前的线程,从Thread的源码中可以看到,的确是有一个ThreadLocalMap实例,其声明和注解如下:

1 /* ThreadLocal values pertaining to this thread. This map is maintained
2 * by the ThreadLocal class. */
3 ThreadLocal.ThreadLocalMap threadLocals = null;

这个属性是专门为ThreadLocal类而存在的,而它的实现也存在于ThreadLocal中,是ThreadLocal的一个静态内部类。其类注释如下:


 1 /**
2 * ThreadLocalMap is a customized hash map suitable only for
3 * maintaining thread local values. No operations are exported
4 * outside of the ThreadLocal class. The class is package private to
5 * allow declaration of fields in class Thread. To help deal with
6 * very large and long-lived usages, the hash table entries use
7 * WeakReferences for keys. However, since reference queues are not
8 * used, stale entries are guaranteed to be removed only when
9 * the table starts running out of space.
10 */

  它是一个定制的HashMap(自然具有HashMap的相关特性,比如自动扩增容量等)。

  前面提到A,B两个问题,从源码来看,A问题的解决方案就是,为每一个Thread维护一个HashMap,在这里就是维护一个ThreadLocalMap属性,这个属性的键是ThreadLocal,值就是资源副本,详细描述如下:

  每一个Thread都有一个ThreadLocalMap属性,这个属性是类似于HashMap的,它以ThreadLocal为键,以属于该线程的资源副本为值。我们可以这样看待ThreadLocal:ThreadLocal是为一组线程维护资源副本的对象,通过它,可以为每一个线程创建资源副本,也可以正确获得属于某一线程的资源副本。

  每一个ThreadLocal只能维护一个共享资源,一旦声明ThreadLocal实例,线程在调用的其get()方法获取资源副本的时候,就可以自动设置绑定到该线程本身。

  好,现在转了一小圈回到get方法()。get()方法的第2、3行很明显是获取属于当前线程的ThreadLocalMap,如果这个map不为空,我们就以当前的ThreadLocal为键,去获取相应的Entry,Entry是ThreadLocalMap的静态内部类,其定义如下:


1 static class Entry extends WeakReference<ThreadLocal> {
2 /** The value associated with this ThreadLocal. */
3 Object value;
4
5 Entry(ThreadLocal k, Object v) {
6 super(k);
7 value = v;
8 }
9 }

  它继承与弱引用,所以在get()方法里面如第7行一样调用e.value方法就可以获取实际的资源副本值。但是如果有一个为空,说明属于该线程的资源副本还不存在,则需要去创建资源副本,从代码中可以看到是调用setInitialValue()方法,其定义如下:


 1 /**
2 * Variant of set() to establish initialValue. Used instead
3 * of set() in case user has overridden the set() method.
4 *
5 * @return the initial value
6 */
7 private T setInitialValue() {
8 T value = initialValue();
9 Thread t = Thread.currentThread();
10 ThreadLocalMap map = getMap(t);
11 if (map != null)
12 map.set(this, value);
13 else
14 createMap(t, value);
15 return value;
16 }

  第8行调用initialValue()方法初始化一个值,还记得在一开始的例子中,我们重载这个方法产生一个初始化的值么?

  接下来是判断线程的ThreadLocalMap是否为空,不为空就直接这是值(键为this,值为value),为空则创建一个Map,调用方法为createMap(),其定义如下:

1 void createMap(Thread t, T firstValue) {
2 t.threadLocals = new ThreadLocalMap(this, firstValue);
3 }

  简单明了,而ThreadLocalMap的这个构造方法的实现如下:


/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
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);
}

  实例化table数组用于存储键值对,然后通过映射将键值对存储进入相应的位置。

  至于set方法,看完get()后应该很简单了,自己都可以实现:


 1 /**
2 * Sets the current thread‘s copy of this thread-local variable
3 * to the specified value. Most subclasses will have no need to
4 * override this method, relying solely on the {@link #initialValue}
5 * method to set the values of thread-locals.
6 *
7 * @param value the value to be stored in the current thread‘s copy of
8 * this thread-local.
9 */
10 public void set(T value) {
11 Thread t = Thread.currentThread();
12 ThreadLocalMap map = getMap(t);
13 if (map != null)
14 map.set(this, value);
15 else
16 createMap(t, value);
17 }

  现在还剩一个问题B,会造成内存泄露吗?ThreadLocal的类注释里面有一段话:

/* Each thread holds an implicit reference to its copy of a thread-local
* variable as long as the thread is alive and the <tt>ThreadLocal</tt>
* instance is accessible; after a thread goes away, all of its copies of
* thread-local instances are subject to garbage collection (unless other
* references to these copies exist).
*/

  每一个线程对资源副本都有一个隐式引用:只要线程还在运行,只要ThreadLocal还是可以获取的。当一个线程运行结束销毁时,所有的资源副本都是可以被垃圾回收的。这段注释表明,ThreadLocal的使用是不会造成内训泄露的。

  但是我们来仔细分析一下,我想画一张Thread、ThreadLocal和ThreadLocalMap的依赖关系图,但是在绘制过程中,我发现:根本就没有依赖!

  非常惊讶,我在这里把ThreadLocal完整的源码贴一遍,读者可以自行审视。

  1 package java.lang;
2 import java.lang.ref.*;
3 import java.util.concurrent.atomic.AtomicInteger;
4
5 /**
6 * This class provides thread-local variables. These variables differ from
7 * their normal counterparts in that each thread that accesses one (via its
8 * <tt>get</tt> or <tt>set</tt> method) has its own, independently initialized
9 * copy of the variable. <tt>ThreadLocal</tt> instances are typically private
10 * static fields in classes that wish to associate state with a thread (e.g.,
11 * a user ID or Transaction ID).
12 *
13 * <p>For example, the class below generates unique identifiers local to each
14 * thread.
15 * A thread‘s id is assigned the first time it invokes <tt>ThreadId.get()</tt>
16 * and remains unchanged on subsequent calls.
17 * <pre>
18 * import java.util.concurrent.atomic.AtomicInteger;
19 *
20 * public class ThreadId {
21 * // Atomic integer containing the next thread ID to be assigned
22 * private static final AtomicInteger nextId = new AtomicInteger(0);
23 *
24 * // Thread local variable containing each thread‘s ID
25 * private static final ThreadLocal&lt;Integer> threadId =
26 * new ThreadLocal&lt;Integer>() {
27 * @Override protected Integer initialValue() {
28 * return nextId.getAndIncrement();
29 * }
30 * };
31 *
32 * // Returns the current thread‘s unique ID, assigning it if necessary
33 * public static int get() {
34 * return threadId.get();
35 * }
36 * }
37 * </pre>
38 * <p>Each thread holds an implicit reference to its copy of a thread-local
39 * variable as long as the thread is alive and the <tt>ThreadLocal</tt>
40 * instance is accessible; after a thread goes away, all of its copies of
41 * thread-local instances are subject to garbage collection (unless other
42 * references to these copies exist).
43 *
44 * @author Josh Bloch and Doug Lea
45 * @since 1.2
46 */
47 public class ThreadLocal<T> {
48 /**
49 * ThreadLocals rely on per-thread linear-probe hash maps attached
50 * to each thread (Thread.threadLocals and
51 * inheritableThreadLocals). The ThreadLocal objects act as keys,
52 * searched via threadLocalHashCode. This is a custom hash code
53 * (useful only within ThreadLocalMaps) that eliminates collisions
54 * in the common case where consecutively constructed ThreadLocals
55 * are used by the same threads, while remaining well-behaved in
56 * less common cases.
57 */
58 private final int threadLocalHashCode = nextHashCode();
59
60 /**
61 * The next hash code to be given out. Updated atomically. Starts at
62 * zero.
63 */
64 private static AtomicInteger nextHashCode =
65 new AtomicInteger();
66
67 /**
68 * The difference between successively generated hash codes - turns
69 * implicit sequential thread-local IDs into near-optimally spread
70 * multiplicative hash values for power-of-two-sized tables.
71 */
72 private static final int HASH_INCREMENT = 0x61c88647;
73
74 /**
75 * Returns the next hash code.
76 */
77 private static int nextHashCode() {
78 return nextHashCode.getAndAdd(HASH_INCREMENT);
79 }
80
81 /**
82 * Returns the current thread‘s "initial value" for this
83 * thread-local variable. This method will be invoked the first
84 * time a thread accesses the variable with the {@link #get}
85 * method, unless the thread previously invoked the {@link #set}
86 * method, in which case the <tt>initialValue</tt> method will not
87 * be invoked for the thread. Normally, this method is invoked at
88 * most once per thread, but it may be invoked again in case of
89 * subsequent invocations of {@link #remove} followed by {@link #get}.
90 *
91 * <p>This implementation simply returns <tt>null</tt>; if the
92 * programmer desires thread-local variables to have an initial
93 * value other than <tt>null</tt>, <tt>ThreadLocal</tt> must be
94 * subclassed, and this method overridden. Typically, an
95 * anonymous inner class will be used.
96 *
97 * @return the initial value for this thread-local
98 */
99 protected T initialValue() {
100 return null;
101 }
102
103 /**
104 * Creates a thread local variable.
105 */
106 public ThreadLocal() {
107 }
108
109 /**
110 * Returns the value in the current thread‘s copy of this
111 * thread-local variable. If the variable has no value for the
112 * current thread, it is first initialized to the value returned
113 * by an invocation of the {@link #initialValue} method.
114 *
115 * @return the current thread‘s value of this thread-local
116 */
117 public T get() {
118 Thread t = Thread.currentThread();
119 ThreadLocalMap map = getMap(t);
120 if (map != null) {
121 ThreadLocalMap.Entry e = map.getEntry(this);
122 if (e != null)
123 return (T)e.value;
124 }
125 return setInitialValue();
126 }
127
128 /**
129 * Variant of set() to establish initialValue. Used instead
130 * of set() in case user has overridden the set() method.
131 *
132 * @return the initial value
133 */
134 private T setInitialValue() {
135 T value = initialValue();
136 Thread t = Thread.currentThread();
137 ThreadLocalMap map = getMap(t);
138 if (map != null)
139 map.set(this, value);
140 else
141 createMap(t, value);
142 return value;
143 }
144
145 /**
146 * Sets the current thread‘s copy of this thread-local variable
147 * to the specified value. Most subclasses will have no need to
148 * override this method, relying solely on the {@link #initialValue}
149 * method to set the values of thread-locals.
150 *
151 * @param value the value to be stored in the current thread‘s copy of
152 * this thread-local.
153 */
154 public void set(T value) {
155 Thread t = Thread.currentThread();
156 ThreadLocalMap map = getMap(t);
157 if (map != null)
158 map.set(this, value);
159 else
160 createMap(t, value);
161 }
162
163 /**
164 * Removes the current thread‘s value for this thread-local
165 * variable. If this thread-local variable is subsequently
166 * {@linkplain #get read} by the current thread, its value will be
167 * reinitialized by invoking its {@link #initialValue} method,
168 * unless its value is {@linkplain #set set} by the current thread
169 * in the interim. This may result in multiple invocations of the
170 * <tt>initialValue</tt> method in the current thread.
171 *
172 * @since 1.5
173 */
174 public void remove() {
175 ThreadLocalMap m = getMap(Thread.currentThread());
176 if (m != null)
177 m.remove(this);
178 }
179
180 /**
181 * Get the map associated with a ThreadLocal. Overridden in
182 * InheritableThreadLocal.
183 *
184 * @param t the current thread
185 * @return the map
186 */
187 ThreadLocalMap getMap(Thread t) {
188 return t.threadLocals;
189 }
190
191 /**
192 * Create the map associated with a ThreadLocal. Overridden in
193 * InheritableThreadLocal.
194 *
195 * @param t the current thread
196 * @param firstValue value for the initial entry of the map
197 * @param map the map to store.
198 */
199 void createMap(Thread t, T firstValue) {
200 t.threadLocals = new ThreadLocalMap(this, firstValue);
201 }
202
203 /**
204 * Factory method to create map of inherited thread locals.
205 * Designed to be called only from Thread constructor.
206 *
207 * @param parentMap the map associated with parent thread
208 * @return a map containing the parent‘s inheritable bindings
209 */
210 static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
211 return new ThreadLocalMap(parentMap);
212 }
213
214 /**
215 * Method childValue is visibly defined in subclass
216 * InheritableThreadLocal, but is internally defined here for the
217 * sake of providing createInheritedMap factory method without
218 * needing to subclass the map class in InheritableThreadLocal.
219 * This technique is preferable to the alternative of embedding
220 * instanceof tests in methods.
221 */
222 T childValue(T parentValue) {
223 throw new UnsupportedOperationException();
224 }
225
226 /**
227 * ThreadLocalMap is a customized hash map suitable only for
228 * maintaining thread local values. No operations are exported
229 * outside of the ThreadLocal class. The class is package private to
230 * allow declaration of fields in class Thread. To help deal with
231 * very large and long-lived usages, the hash table entries use
232 * WeakReferences for keys. However, since reference queues are not
233 * used, stale entries are guaranteed to be removed only when
234 * the table starts running out of space.
235 */
236 static class ThreadLocalMap {
237
238 /**
239 * The entries in this hash map extend WeakReference, using
240 * its main ref field as the key (which is always a
241 * ThreadLocal object). Note that null keys (i.e. entry.get()
242 * == null) mean that the key is no longer referenced, so the
243 * entry can be expunged from table. Such entries are referred to
244 * as "stale entries" in the code that follows.
245 */
246 static class Entry extends WeakReference<ThreadLocal> {
247 /** The value associated with this ThreadLocal. */
248 Object value;
249
250 Entry(ThreadLocal k, Object v) {
251 super(k);
252 value = v;
253 }
254 }
255
256 /**
257 * The initial capacity -- MUST be a power of two.
258 */
259 private static final int INITIAL_CAPACITY = 16;
260
261 /**
262 * The table, resized as necessary.
263 * table.length MUST always be a power of two.
264 */
265 private Entry[] table;
266
267 /**
268 * The number of entries in the table.
269 */
270 private int size = 0;
271
272 /**
273 * The next size value at which to resize.
274 */
275 private int threshold; // Default to 0
276
277 /**
278 * Set the resize threshold to maintain at worst a 2/3 load factor.
279 */
280 private void setThreshold(int len) {
281 threshold = len * 2 / 3;
282 }
283
284 /**
285 * Increment i modulo len.
286 */
287 private static int nextIndex(int i, int len) {
288 return ((i + 1 < len) ? i + 1 : 0);
289 }
290
291 /**
292 * Decrement i modulo len.
293 */
294 private static int prevIndex(int i, int len) {
295 return ((i - 1 >= 0) ? i - 1 : len - 1);
296 }
297
298 /**
299 * Construct a new map initially containing (firstKey, firstValue).
300 * ThreadLocalMaps are constructed lazily, so we only create
301 * one when we have at least one entry to put in it.
302 */
303 ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
304 table = new Entry[INITIAL_CAPACITY];
305 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
306 table[i] = new Entry(firstKey, firstValue);
307 size = 1;
308 setThreshold(INITIAL_CAPACITY);
309 }
310
311 /**
312 * Construct a new map including all Inheritable ThreadLocals
313 * from given parent map. Called only by createInheritedMap.
314 *
315 * @param parentMap the map associated with parent thread.
316 */
317 private ThreadLocalMap(ThreadLocalMap parentMap) {
318 Entry[] parentTable = parentMap.table;
319 int len = parentTable.length;
320 setThreshold(len);
321 table = new Entry[len];
322
323 for (int j = 0; j < len; j++) {
324 Entry e = parentTable[j];
325 if (e != null) {
326 ThreadLocal key = e.get();
327 if (key != null) {
328 Object value = key.childValue(e.value);
329 Entry c = new Entry(key, value);
330 int h = key.threadLocalHashCode & (len - 1);
331 while (table[h] != null)
332 h = nextIndex(h, len);
333 table[h] = c;
334 size++;
335 }
336 }
337 }
338 }
339
340 /**
341 * Get the entry associated with key. This method
342 * itself handles only the fast path: a direct hit of existing
343 * key. It otherwise relays to getEntryAfterMiss. This is
344 * designed to maximize performance for direct hits, in part
345 * by making this method readily inlinable.
346 *
347 * @param key the thread local object
348 * @return the entry associated with key, or null if no such
349 */
350 private Entry getEntry(ThreadLocal key) {
351 int i = key.threadLocalHashCode & (table.length - 1);
352 Entry e = table[i];
353 if (e != null && e.get() == key)
354 return e;
355 else
356 return getEntryAfterMiss(key, i, e);
357 }
358
359 /**
360 * Version of getEntry method for use when key is not found in
361 * its direct hash slot.
362 *
363 * @param key the thread local object
364 * @param i the table index for key‘s hash code
365 * @param e the entry at table[i]
366 * @return the entry associated with key, or null if no such
367 */
368 private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
369 Entry[] tab = table;
370 int len = tab.length;
371
372 while (e != null) {
373 ThreadLocal k = e.get();
374 if (k == key)
375 return e;
376 if (k == null)
377 expungeStaleEntry(i);
378 else
379 i = nextIndex(i, len);
380 e = tab[i];
381 }
382 return null;
383 }
384
385 /**
386 * Set the value associated with key.
387 *
388 * @param key the thread local object
389 * @param value the value to be set
390 */
391 private void set(ThreadLocal key, Object value) {
392
393 // We don‘t use a fast path as with get() because it is at
394 // least as common to use set() to create new entries as
395 // it is to replace existing ones, in which case, a fast
396 // path would fail more often than not.
397
398 Entry[] tab = table;
399 int len = tab.length;
400 int i = key.threadLocalHashCode & (len-1);
401
402 for (Entry e = tab[i];
403 e != null;
404 e = tab[i = nextIndex(i, len)]) {
405 ThreadLocal k = e.get();
406
407 if (k == key) {
408 e.value = value;
409 return;
410 }
411
412 if (k == null) {
413 replaceStaleEntry(key, value, i);
414 return;
415 }
416 }
417
418 tab[i] = new Entry(key, value);
419 int sz = ++size;
420 if (!cleanSomeSlots(i, sz) && sz >= threshold)
421 rehash();
422 }
423
424 /**
425 * Remove the entry for key.
426 */
427 private void remove(ThreadLocal key) {
428 Entry[] tab = table;
429 int len = tab.length;
430 int i = key.threadLocalHashCode & (len-1);
431 for (Entry e = tab[i];
432 e != null;
433 e = tab[i = nextIndex(i, len)]) {
434 if (e.get() == key) {
435 e.clear();
436 expungeStaleEntry(i);
437 return;
438 }
439 }
440 }
441
442 /**
443 * Replace a stale entry encountered during a set operation
444 * with an entry for the specified key. The value passed in
445 * the value parameter is stored in the entry, whether or not
446 * an entry already exists for the specified key.
447 *
448 * As a side effect, this method expunges all stale entries in the
449 * "run" containing the stale entry. (A run is a sequence of entries
450 * between two null slots.)
451 *
452 * @param key the key
453 * @param value the value to be associated with key
454 * @param staleSlot index of the first stale entry encountered while
455 * searching for key.
456 */
457 private void replaceStaleEntry(ThreadLocal key, Object value,
458 int staleSlot) {
459 Entry[] tab = table;
460 int len = tab.length;
461 Entry e;
462
463 // Back up to check for prior stale entry in current run.
464 // We clean out whole runs at a time to avoid continual
465 // incremental rehashing due to garbage collector freeing
466 // up refs in bunches (i.e., whenever the collector runs).
467 int slotToExpunge = staleSlot;
468 for (int i = prevIndex(staleSlot, len);
469 (e = tab[i]) != null;
470 i = prevIndex(i, len))
471 if (e.get() == null)
472 slotToExpunge = i;
473
474 // Find either the key or trailing null slot of run, whichever
475 // occurs first
476 for (int i = nextIndex(staleSlot, len);
477 (e = tab[i]) != null;
478 i = nextIndex(i, len)) {
479 ThreadLocal k = e.get();
480
481 // If we find key, then we need to swap it
482 // with the stale entry to maintain hash table order.
483 // The newly stale slot, or any other stale slot
484 // encountered above it, can then be sent to expungeStaleEntry
485 // to remove or rehash all of the other entries in run.
486 if (k == key) {
487 e.value = value;
488
489 tab[i] = tab[staleSlot];
490 tab[staleSlot] = e;
491
492 // Start expunge at preceding stale entry if it exists
493 if (slotToExpunge == staleSlot)
494 slotToExpunge = i;
495 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
496 return;
497 }
498
499 // If we didn‘t find stale entry on backward scan, the
500 // first stale entry seen while scanning for key is the
501 // first still present in the run.
502 if (k == null && slotToExpunge == staleSlot)
503 slotToExpunge = i;
504 }
505
506 // If key not found, put new entry in stale slot
507 tab[staleSlot].value = null;
508 tab[staleSlot] = new Entry(key, value);
509
510 // If there are any other stale entries in run, expunge them
511 if (slotToExpunge != staleSlot)
512 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
513 }
514
515 /**
516 * Expunge a stale entry by rehashing any possibly colliding entries
517 * lying between staleSlot and the next null slot. This also expunges
518 * any other stale entries encountered before the trailing null. See
519 * Knuth, Section 6.4
520 *
521 * @param staleSlot index of slot known to have null key
522 * @return the index of the next null slot after staleSlot
523 * (all between staleSlot and this slot will have been checked
524 * for expunging).
525 */
526 private int expungeStaleEntry(int staleSlot) {
527 Entry[] tab = table;
528 int len = tab.length;
529
530 // expunge entry at staleSlot
531 tab[staleSlot].value = null;
532 tab[staleSlot] = null;
533 size--;
534
535 // Rehash until we encounter null
536 Entry e;
537 int i;
538 for (i = nextIndex(staleSlot, len);
539 (e = tab[i]) != null;
540 i = nextIndex(i, len)) {
541 ThreadLocal k = e.get();
542 if (k == null) {
543 e.value = null;
544 tab[i] = null;
545 size--;
546 } else {
547 int h = k.threadLocalHashCode & (len - 1);
548 if (h != i) {
549 tab[i] = null;
550
551 // Unlike Knuth 6.4 Algorithm R, we must scan until
552 // null because multiple entries could have been stale.
553 while (tab[h] != null)
554 h = nextIndex(h, len);
555 tab[h] = e;
556 }
557 }
558 }
559 return i;
560 }
561
562 /**
563 * Heuristically scan some cells looking for stale entries.
564 * This is invoked when either a new element is added, or
565 * another stale one has been expunged. It performs a
566 * logarithmic number of scans, as a balance between no
567 * scanning (fast but retains garbage) and a number of scans
568 * proportional to number of elements, that would find all
569 * garbage but would cause some insertions to take O(n) time.
570 *
571 * @param i a position known NOT to hold a stale entry. The
572 * scan starts at the element after i.
573 *
574 * @param n scan control: <tt>log2(n)</tt> cells are scanned,
575 * unless a stale entry is found, in which case
576 * <tt>log2(table.length)-1</tt> additional cells are scanned.
577 * When called from insertions, this parameter is the number
578 * of elements, but when from replaceStaleEntry, it is the
579 * table length. (Note: all this could be changed to be either
580 * more or less aggressive by weighting n instead of just
581 * using straight log n. But this version is simple, fast, and
582 * seems to work well.)
583 *
584 * @return true if any stale entries have been removed.
585 */
586 private boolean cleanSomeSlots(int i, int n) {
587 boolean removed = false;
588 Entry[] tab = table;
589 int len = tab.length;
590 do {
591 i = nextIndex(i, len);
592 Entry e = tab[i];
593 if (e != null && e.get() == null) {
594 n = len;
595 removed = true;
596 i = expungeStaleEntry(i);
597 }
598 } while ( (n >>>= 1) != 0);
599 return removed;
600 }
601
602 /**
603 * Re-pack and/or re-size the table. First scan the entire
604 * table removing stale entries. If this doesn‘t sufficiently
605 * shrink the size of the table, double the table size.
606 */
607 private void rehash() {
608 expungeStaleEntries();
609
610 // Use lower threshold for doubling to avoid hysteresis
611 if (size >= threshold - threshold / 4)
612 resize();
613 }
614
615 /**
616 * Double the capacity of the table.
617 */
618 private void resize() {
619 Entry[] oldTab = table;
620 int oldLen = oldTab.length;
621 int newLen = oldLen * 2;
622 Entry[] newTab = new Entry[newLen];
623 int count = 0;
624
625 for (int j = 0; j < oldLen; ++j) {
626 Entry e = oldTab[j];
627 if (e != null) {
628 ThreadLocal k = e.get();
629 if (k == null) {
630 e.value = null; // Help the GC
631 } else {
632 int h = k.threadLocalHashCode & (newLen - 1);
633 while (newTab[h] != null)
634 h = nextIndex(h, newLen);
635 newTab[h] = e;
636 count++;
637 }
638 }
639 }
640
641 setThreshold(newLen);
642 size = count;
643 table = newTab;
644 }
645
646 /**
647 * Expunge all stale entries in the table.
648 */
649 private void expungeStaleEntries() {
650 Entry[] tab = table;
651 int len = tab.length;
652 for (int j = 0; j < len; j++) {
653 Entry e = tab[j];
654 if (e != null && e.get() == null)
655 expungeStaleEntry(j);
656 }
657 }
658 }
659 }

  ThreadLocal对Thread的引用全部通过局部变量完成,而没有一个全局变量。而实际的资源副本则存储在Thread的自身的属性ThreadLocalMap中,这说明,其实ThreadLocal只是关联一个Thread和其资源副本的桥梁,并且实际上Thread和资源副本的生命周期是紧密相连的,的的确确如ThreadLocal所说,在线程被回收的时候,其资源副本也会被回收,虽然ThreadLocal是静态的,但是它既不引用Thread,也不引用ThreadLocalMap。真是精巧的设计!

五、ThreadLocal作用

其实ThreadLocal所实现的功能前面已经描述的很清楚了,但是从网上的讨论来看,大家对ThreadLocal所要达成的目的意见却不是很统一,比如博客《彻底理解ThreadLocal》,具体可以看一下类的注释:


/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* <tt>get</tt> or <tt>set</tt> method) has its own, independently initialized
* copy of the variable. <tt>ThreadLocal</tt> instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
*/

从这个注解来看,和TLS(可以参见《Thread Local
Storage》
)大致是一样的,其实不用去深究其具体目的。个人觉得,ThreadLocal的功能就是为每一个线程提供一个资源副本,当你有这样的需求的时候,就可以使用ThreadLocal去解决问题。

【Java】深入理解ThreadLocal,布布扣,bubuko.com

时间: 2024-12-26 14:23:24

【Java】深入理解ThreadLocal的相关文章

Java 并发:深入理解 ThreadLocal

摘要: ThreadLocal 又名线程局部变量,是 Java 中一种较为特殊的线程绑定机制,用于保证变量在不同线程间的隔离性,以方便每个线程处理自己的状态.进一步地,本文以ThreadLocal类的源码为切入点,深入分析了ThreadLocal类的作用原理,并给出应用场景和一般使用步骤. 一. 对 ThreadLocal 的理解 1). ThreadLocal 概述 ThreadLocal 又名 线程局部变量,是 Java 中一种较为特殊的 线程绑定机制,可以为每一个使用该变量的线程都提供一个

理解ThreadLocal背后的概念

介绍 我之前在任何场合都没有使用过thread local,因此没有注意到它,直到最近用到它的时候. 前提信息 线程可以理解为一个单独的进程,它有自己的调用栈.在java中每一个线程都有一个调用栈或者说每一个调用栈都有一个线程,即使你不在你的程序中创建线程,线程仍然会在你不知道的情况下运行.最简单的例子就是,当你通过main方法启动一个简单的java程序时,你不在程序里调用new Thread().start(),但是JVM仍然会创建一个main thread 去运行main方法. main线程

Java学习笔记--ThreadLocal

我们知道Spring通过各种模板类降低了开发者使用各种数据持久技术的难度.这些模板类都是线程安全的,也就是说,多个DAO可以复用同一个模板实例而不会发生冲突.我们使用模板类访问底层数据,根据持久化技术的不同,模板类需要绑定数据连接或会话的资源.但这些资源本身是非线程安全的,也就是说它们不能在同一时刻被多个线程共享.虽然模板类通过资源池获取数据连接或会话,但资源池本身解决的是数据连接或会话的缓存问题,并非数据连接或会话的线程安全问题. 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对

JAVA学习篇--ThreadLocal,Java中特殊的线程绑定机制

在DRP项目中,我们使用了ThreadLocal来创建Connection连接,避免了一直以参数的形式将Connection向下传递(传递connection的目的是由于jdbc事务要求确保使用同一个connection连接).那么ThreadLocal是如果做到的呢?它和同步锁的不同在哪里? 是什么: 对于ThreadLocal看英文单词我们很容易理解为一个线程的本地实现,但是它并不是一个Thread,而是threadlocalvariable(线程局部变量).也许把它命名为ThreadLoc

理解ThreadLocal类

1 ThreadLocal是什么 早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地编写出优美的多线程程序. ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象.当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本.所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应

[JavaEE]理解ThreadLocal

转http://www.iteye.com/topic/103804 首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.各个线程中访问的是不同的对象. 另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什

理解ThreadLocal(二)

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.各个线程中访问的是不同的对象. 另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本.通过ThreadLocal.set()将这个新创建

理解ThreadLocal(一)

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地编写出优美的多线程程序. ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”.其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些. 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变

一个例子理解threadLocal用法

ThreadLocal可以使对象达到线程隔离的目的.话不多说直接上代码: /** * 一个例子理解threadLocal * * 一个单例模式的类 */ public class SingleThreadLocalTest { private static SingleThreadLocalTest single = new SingleThreadLocalTest(); private ThreadLocal<String> threadLocal = new ThreadLocal<