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.
/** * Maintain variable for every thread, to avoid effect other thread */ privatestaticfinal ThreadLocal<Object> CONTEXT_HOLDER = ThreadLocal.withInitial(DataSourceKey.master);
/** * All DataSource List */ publicstatic List<Object> dataSourceKeys = newArrayList<>();
/** * Get current DataSource * * @return data source key */ publicstatic String getDataSourceKey() { return CONTEXT_HOLDER.get(); }
/** * To set DataSource as default */ publicstaticvoidclearDataSourceKey() { CONTEXT_HOLDER.remove(); }
/** * Check if give DataSource is in current DataSource list * * @param key the key * @return boolean boolean */ publicstaticbooleancontainDataSourceKey(String key) { return dataSourceKeys.contains(key); } }
/** * 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. */ publicvoidset(T value) { // 获取当前线程 Threadt= Thread.currentThread(); // 获取维护当前线程变量的ThreadLocalMap数据,一种类似于HashMap的数据结构 ThreadLocalMapmap= getMap(t); // 如果当前线程已经存在了Map,直接调用map.set if (map != null) map.set(this, value); // 不存在Map,则先进行新增map,再进行set else createMap(t, value); }
查看源码发现set()方法中使用到了ThreadLocalMap类。
展开查看ThreadLocalMap类源码
staticclassThreadLocalMap {
/** * 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. */ staticclassEntryextendsWeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value;
/** * The initial capacity -- MUST be a power of two. */ privatestaticfinalintINITIAL_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. */ privateintsize=0;
/** * The next size value at which to resize. */ privateint threshold; // Default to 0
/** * Set the resize threshold to maintain at worst a 2/3 load factor. */ privatevoidsetThreshold(int len) { threshold = len * 2 / 3; }
/** * Increment i modulo len. */ privatestaticintnextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }
/** * Decrement i modulo len. */ privatestaticintprevIndex(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 = newEntry[INITIAL_CAPACITY]; inti= firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = newEntry(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. */ privateThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; intlen= parentTable.length; setThreshold(len); table = newEntry[len];
for (intj=0; j < len; j++) { Entrye= parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Objectvalue= key.childValue(e.value); Entryc=newEntry(key, value); inth= 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) { inti= key.threadLocalHashCode & (table.length - 1); Entrye= 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; intlen= 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]; } returnnull; }
/** * Set the value associated with key. * * @param key the thread local object * @param value the value to be set */ privatevoidset(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.
/** * Remove the entry for key. */ privatevoidremove(ThreadLocal<?> key) { Entry[] tab = table; intlen= tab.length; inti= key.threadLocalHashCode & (len-1); for (Entrye= 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. */ privatevoidreplaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) { Entry[] tab = table; intlen= 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). intslotToExpunge= staleSlot; for (inti= 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 (inti= 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] = newEntry(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). */ privateintexpungeStaleEntry(int staleSlot) { Entry[] tab = table; intlen= tab.length;
// 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 { inth= 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: {@code log2(n)} cells are scanned, * unless a stale entry is found, in which case * {@code 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. */ privatebooleancleanSomeSlots(int i, int n) { booleanremoved=false; Entry[] tab = table; intlen= tab.length; do { i = nextIndex(i, len); Entrye= 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. */ privatevoidrehash() { expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis if (size >= threshold - threshold / 4) resize(); }
/** * Double the capacity of the table. */ privatevoidresize() { Entry[] oldTab = table; intoldLen= oldTab.length; intnewLen= oldLen * 2; Entry[] newTab = newEntry[newLen]; intcount=0;
for (intj=0; j < oldLen; ++j) { Entrye= oldTab[j]; if (e != null) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; // Help the GC } else { inth= k.threadLocalHashCode & (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } }