首页 > 代码库 > 【Java】深入理解ThreadLocal
【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 =http://www.mamicode.com/ 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 =http://www.mamicode.com/ 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<Integer> threadId = 26 * new ThreadLocal<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 =http://www.mamicode.com/ 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 =http://www.mamicode.com/ 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 =http://www.mamicode.com/ 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 =http://www.mamicode.com/ 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 =http://www.mamicode.com/ 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 = http://www.mamicode.com/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 = http://www.mamicode.com/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 = http://www.mamicode.com/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 = http://www.mamicode.com/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去解决问题。