ThreadLocal<T>类:以空间换时间提供一种多线程更快捷访问变量的方式。这种方式不存在竞争,所以也不存在并发的安全性问题。
//---------------------------------------------------update:2017/3/21------------------------------------------------------
API:
构造方法摘要 | |
---|---|
() :创建一个线程本地变量。 |
方法摘要 | |
---|---|
| () :返回此线程局部变量的当前线程副本中的值。 |
protected | () : 返回此线程局部变量的当前线程的“初始值”。 |
void | () : 移除此线程局部变量当前线程的值。 |
void | ( value) : 将此线程局部变量的当前线程副本中的值设置为指定值。 |
API说明:
每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)
//-------------------------------------------------------------------------------------------------------------------------
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via itsget or set method) has its own, independently initialized copy of the variable.ThreadLocal instances are typically privatestatic fields in classes that wish to associate state with a thread
源码注释对类的解释如上:
此类提供一个线程的本地变量,这个变量不同于普通变量,每个线程都拥有自己的get,set方法,和独立的初始化副本,ThreadLocal实例通常来说都是private static类型。这个解释已经很清楚明了,结合示例通俗的讲就是说对于多个线程操作的同一个变量,我们给每一个线程copy一个变量的副本存到线程中(线程中有个map变量)。这样每个线程操作自己的副本就可以了,不会出现同时操作同一个变量的问题(即是资源竞争的问题)。不存在竞争,就不存在并发的安全性问题。
一、示例:
public class NumActionThreadLoal { private ThreadLocalnum = new ThreadLocal (){ @Override protected Integer initialValue() { return new Integer(0); } }; public ThreadLocal getNum() { return num; } public void setNum(ThreadLocal num) { this.num = num; } public void addMethod(){ for (int i = 0; i < 100; i++) { num.set(num.get()+1); } }}
我们用ThreadLocal来修饰了一个Integer类型的变量,并给这个变量了get,set方法,并且提供了一个addMetod的方法为这个变量自增一百次。
下面我们用三个线程去访问这个变量:
public class Loaclthreadtest extends Thread{ private static NumActionThreadLoal nt=new NumActionThreadLoal() ; @Override public void run() { nt.addMethod(); System.out.println(Thread.currentThread().getName()+" "+nt.getNum().get()); } public static void main(String[] args) { Loaclthreadtest l1 = new Loaclthreadtest(); Loaclthreadtest l2 = new Loaclthreadtest(); Loaclthreadtest l3 = new Loaclthreadtest(); Thread t1 = new Thread(l1); Thread t2 = new Thread(l2); Thread t3 = new Thread(l3); t1.start(); t2.start(); t3.start(); System.out.println(Thread.currentThread().getName()+" "+nt.getNum().get()); } }
三个线程启动分别对nt这个对象调用了addMethod方法,按道理说三个线程分别对同一个对象调用自增一百次的操作结果应该是300。但是事实结果如下:
main 0Thread-4 100Thread-3 100Thread-5 100
主线程获取到的nt的num变量值为0,其他三个线程分别是100,如果从单一个线程来讲这三个线程各自的操作是正确的。都是自增了一百。但是为什么跟我们预想的不一样,操作的是同一个对象的同一个变量,结果却是单独操作了这个变量。从这个结果中我们看出了确实是符合注释所说的为每个线程提供了独立的操作副本。
二、ThreadLocal具体实现源码:
ThreadLocal类的全部源码:方便一步到位,把握全局。
* Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved.package java.lang;import java.lang.ref.*;import java.util.concurrent.atomic.AtomicInteger;/** * This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread that accesses one (via its * get or set method) has its own, independently initialized * copy of the variable. ThreadLocal instances are typically private * static fields in classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID). * *For example, the class below generates unique identifiers local to each * thread. * A thread's id is assigned the first time it invokes ThreadId.get() * and remains unchanged on subsequent calls. *
* import java.util.concurrent.atomic.AtomicInteger; * * public class ThreadId { * // Atomic integer containing the next thread ID to be assigned * private static final AtomicInteger nextId = new AtomicInteger(0); * * // Thread local variable containing each thread's ID * private static final ThreadLocal<Integer> threadId = * new ThreadLocal<Integer>() { * @Override protected Integer initialValue() { * return nextId.getAndIncrement(); * } * }; * * // Returns the current thread's unique ID, assigning it if necessary * public static int get() { * return threadId.get(); * } * } **Each thread holds an implicit reference to its copy of a thread-local * variable as long as the thread is alive and the ThreadLocal * 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). * * @author Josh Bloch and Doug Lea * @since 1.2 */public class ThreadLocal
{ /** * ThreadLocals rely on per-thread linear-probe hash maps attached * to each thread (Thread.threadLocals and * inheritableThreadLocals). The ThreadLocal objects act as keys, * searched via threadLocalHashCode. This is a custom hash code * (useful only within ThreadLocalMaps) that eliminates collisions * in the common case where consecutively constructed ThreadLocals * are used by the same threads, while remaining well-behaved in * less common cases. */ private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Updated atomically. Starts at * zero. */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647; /** * Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } /** * Returns the current thread's "initial value" for this * thread-local variable. This method will be invoked the first * time a thread accesses the variable with the { @link #get} * method, unless the thread previously invoked the { @link #set} * method, in which case the initialValue method will not * be invoked for the thread. Normally, this method is invoked at * most once per thread, but it may be invoked again in case of * subsequent invocations of { @link #remove} followed by { @link #get}. * * This implementation simply returns null; if the * programmer desires thread-local variables to have an initial * value other than null, ThreadLocal must be * subclassed, and this method overridden. Typically, an * anonymous inner class will be used. * * @return the initial value for this thread-local */ protected T initialValue() { return null; } /** * Creates a thread local variable. */ public ThreadLocal() { } /** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {
@link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); } /** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } /** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the { @link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } /** * Removes the current thread's value for this thread-local * variable. If this thread-local variable is subsequently * { @linkplain #get read} by the current thread, its value will be * reinitialized by invoking its { @link #initialValue} method, * unless its value is { @linkplain #set set} by the current thread * in the interim. This may result in multiple invocations of the * initialValue method in the current thread. * * @since 1.5 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } /** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } /** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map * @param map the map to store. */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } /** * Factory method to create map of inherited thread locals. * Designed to be called only from Thread constructor. * * @param parentMap the map associated with parent thread * @return a map containing the parent's inheritable bindings */ static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); } /** * Method childValue is visibly defined in subclass * InheritableThreadLocal, but is internally defined here for the * sake of providing createInheritedMap factory method without * needing to subclass the map class in InheritableThreadLocal. * This technique is preferable to the alternative of embedding * instanceof tests in methods. */ T childValue(T parentValue) { throw new UnsupportedOperationException(); } /** * ThreadLocalMap is a customized hash map suitable only for * maintaining thread local values. No operations are exported * outside of the ThreadLocal class. The class is package private to * allow declaration of fields in class Thread. To help deal with * very large and long-lived usages, the hash table entries use * WeakReferences for keys. However, since reference queues are not * used, stale entries are guaranteed to be removed only when * the table starts running out of space. */ static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference{ /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } /** * The initial capacity -- MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16; /** * The table, resized as necessary. * table.length MUST always be a power of two. */ private Entry[] table; /** * The number of entries in the table. */ private int size = 0; /** * The next size value at which to resize. */ private int threshold; // Default to 0 /** * Set the resize threshold to maintain at worst a 2/3 load factor. */ private void setThreshold(int len) { threshold = len * 2 / 3; } /** * Increment i modulo len. */ private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } /** * Decrement i modulo len. */ private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); } /** * 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); } /** * Construct a new map including all Inheritable ThreadLocals * from given parent map. Called only by createInheritedMap. * * @param parentMap the map associated with parent thread. */ 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) { ThreadLocal key = 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++; } } } } /** * Get the entry associated with key. This method * itself handles only the fast path: a direct hit of existing * key. It otherwise relays to getEntryAfterMiss. This is * designed to maximize performance for direct hits, in part * by making this method readily inlinable. * * @param key the thread local object * @return the entry associated with key, or null if no such */ 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); } /** * Version of getEntry method for use when key is not found in * its direct hash slot. * * @param key the thread local object * @param i the table index for key's hash code * @param e the entry at table[i] * @return the entry associated with key, or null if no such */ 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; } /** * Set the value associated with key. * * @param key the thread local object * @param value the value to be set */ private void set(ThreadLocal key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. 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(); } /** * Remove the entry for key. */ 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; } } } /** * Replace a stale entry encountered during a set operation * with an entry for the specified key. The value passed in * the value parameter is stored in the entry, whether or not * an entry already exists for the specified key. * * As a side effect, this method expunges all stale entries in the * "run" containing the stale entry. (A run is a sequence of entries * between two null slots.) * * @param key the key * @param value the value to be associated with key * @param staleSlot index of the first stale entry encountered while * searching for key. */ private void replaceStaleEntry(ThreadLocal key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; // Back up to check for prior stale entry in current run. // We clean out whole runs at a time to avoid continual // incremental rehashing due to garbage collector freeing // up refs in bunches (i.e., whenever the collector runs). int slotToExpunge = staleSlot; for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; // Find either the key or trailing null slot of run, whichever // occurs first for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal k = e.get(); // If we find key, then we need to swap it // with the stale entry to maintain hash table order. // The newly stale slot, or any other stale slot // encountered above it, can then be sent to expungeStaleEntry // to remove or rehash all of the other entries in run. if (k == key) { e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e; // Start expunge at preceding stale entry if it exists if (slotToExpunge == staleSlot) slotToExpunge = i; cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } // If we didn't find stale entry on backward scan, the // first stale entry seen while scanning for key is the // first still present in the run. if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } // If key not found, put new entry in stale slot tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // If there are any other stale entries in run, expunge them if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); } /** * Expunge a stale entry by rehashing any possibly colliding entries * lying between staleSlot and the next null slot. This also expunges * any other stale entries encountered before the trailing null. See * Knuth, Section 6.4 * * @param staleSlot index of slot known to have null key * @return the index of the next null slot after staleSlot * (all between staleSlot and this slot will have been checked * for expunging). */ private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size--; // Rehash until we encounter 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; // Unlike Knuth 6.4 Algorithm R, we must scan until // null because multiple entries could have been stale. while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; } /** * Heuristically scan some cells looking for stale entries. * This is invoked when either a new element is added, or * another stale one has been expunged. It performs a * logarithmic number of scans, as a balance between no * scanning (fast but retains garbage) and a number of scans * proportional to number of elements, that would find all * garbage but would cause some insertions to take O(n) time. * * @param i a position known NOT to hold a stale entry. The * scan starts at the element after i. * * @param n scan control: log2(n) cells are scanned, * unless a stale entry is found, in which case * log2(table.length)-1 additional cells are scanned. * When called from insertions, this parameter is the number * of elements, but when from replaceStaleEntry, it is the * table length. (Note: all this could be changed to be either * more or less aggressive by weighting n instead of just * using straight log n. But this version is simple, fast, and * seems to work well.) * * @return true if any stale entries have been removed. */ 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; } /** * Re-pack and/or re-size the table. First scan the entire * table removing stale entries. If this doesn't sufficiently * shrink the size of the table, double the table size. */ private void rehash() { expungeStaleEntries(); // Use lower threshold for doubling to avoid hysteresis if (size >= threshold - threshold / 4) resize(); } /** * Double the capacity of the table. */ 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; // Help the 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; } /** * Expunge all stale entries in the table. */ 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); } } }}
这个类分两部分,一部分是ThreadLocal类,一部分是ThreadLocalMap这个内部类。我们先看ThreadLocal这部分。
1、属性:threadLocalHashCode.这个属性是来唯一标识这个对象的值,是一个自定义hashcode.
private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Updated atomically. Starts at * zero. */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647; /** * Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
上面这部分代码我把它整成一条代码:这样看更清晰,这个变量是final不可变的,并且他是一个AtomicInteger原子类型的值。
private final int threadLocalHashCode = new AtomicInteger().getAndAdd(HASH_INCREMENT);
看一下对变量注释:
/** * ThreadLocals rely on per-thread linear-probe hash maps attached * to each thread (Thread.threadLocals and * inheritableThreadLocals). The ThreadLocal objects act as keys, * searched via threadLocalHashCode. This is a custom hash code * (useful only within ThreadLocalMaps) that eliminates collisions * in the common case where consecutively constructed ThreadLocals * are used by the same threads, while remaining well-behaved in * less common cases. */
ThreadLocals依靠每个线程的map映射,ThreadLocal对象作为键,所修饰的变量作为值,通过查询threadLocalHashCode的值来定位一个ThreadLocal对象从而确定值。
2、方法:方法中我们可以看到这个副本变量是怎样产生,怎样操作的。
get:get方法是线程获取变量的值。
/** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the { @link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
getMap:get方法所调用的getMap方法获取Map,getMap方法中返回这个线程的一个属性threadLocals,他是个ThreadLocalMap类型的map。
/** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
threadLocals变量:这个变量是定义在Thread类中的变量,是属于线程的属性。
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
getEntry:可以看到源码是通过对象的threadLocalHashCode定位值所在的位置,然后将其返回。
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); }
setInitialValue:当map==null的时候,是第一次访问,还未创建副本,creatMap创建副本,最后返回value(这个value是初始化方法的返回值)。
/** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
creatMap:给线程的变量threadLocals初始化。
/** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map * @param map the map to store. */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
三、如下图所示一个访问的流程:
四、总的来说:
线程调用get方法是获取到了调用这个get方法的线程TX(Thread.currentThread),并且从这个线程TX中获取到了个ThreadLocal.ThreadLocalMap类型的map即threadLocals,然后对这个map通过getEntry()方法获取到这个ThreadLocal修饰变量的值。(当然如果你获取到的map=null,这是他的初始值,只是声明并没有对这个 map对象进行初始化和副本copy,此时调用setInitialValue方法通过creatMap(thread t, initialValue)来给这个线程TX的threadLocals进行初始化y,键:ThreadLocal对象,值:initvalue()(这个initialValue()是我们定义对象的时候重写initialValue()方法的返回值。))
但是无论是get,还是set,remove...等方法操作的对象始终是threadLocals这个ThreadLocalMap类的实例对象。所以最终调用的是ThreadLocalMap类中的方法。
五、ThreadLocalMap类:里面主要是一些map的实现和操作方法,是ThreadLocal对象操作的基础。
具体这些方法实现大家可以自己去看。
六、ThreadLocal和Synchonrized
很多地方说ThreadLocal和synconrized都是解决并发问题的方案,但是个人认为更准确的来说应该是ThreadLocal提供了一种避免并发(前提是可以避免)的方案,但是其局限性很明显,各线程之间数据不可见。一旦我们要求操作可见,并且数据一致的时候,他是完全解决不了的。所以说一个真正的并发问题,这种解决方案是完全规避不了的。更多的时候感觉它更像是在做工厂模式做的一件事,为每个需要这个对象的线程提供一个副本。如果说ThreadLocal在它所能解决的问题中所体现的相比于Synchonrized的优势那应该是他让线程彼此免于排队等待。