結論:內部通過,操作當前線程的成員變量threadLocalMap,即Thread.currentThread.threadLocalMap,Map中key為當前線程的threadLocal對象(即調用add()方法的實例對象this,本文中為threadLocalUser),value為存入對象,當存取時內部自動傳入當前線程key,所以只能取到當前線程的綁定變量,從而實現線程級隔離。當線程不同時操作的thread也不同,Map當然也不同,map中的key和value也就不同了。
打個不太恰當的比喻,多個It男(Thread),每個人一個背包(threadLocalMap),包里放入自己經常要用的東西比如筆記本(key標簽為"筆忘本",value"聯想電腦",沒有的話,第一次參加工作createMap要用的時候也會買了放進去),當多個人同時都接到接到任務“拿出筆記本”(並發http請求),自己在任何地方都可以把“筆忘本”拿出來用,但每個人都不會拿錯。
注:map的key不是線程id或者name,而是當前線程下的ThreadLocal實例對象,之前所以這么設計,保證當前線程可以有多個ThreadLocal對象,如果key為當前線程,那就限制了當前線程只能存儲一個對象
如
ThreadLocal<Student> threadLocalStudent=new ThreadLocal();
ThreadLocal<UserInfo> threadLocalUserInfo=new new ThreadLocal();
為方便分析本文以一個ThreadLocal對象來舉例,並且為方便線程內跨類跨包隨取隨用,用自定義類MyThreadLocal來將ThreadLocal成員封裝為靜態對象,並為之提供訪問方法set ,get
1.作用:
用於存儲線程中共享變量,隨用隨取,如操作員信息,一處存入處處可用
2.原理如下
存:Class加載初始化時將threadlocal變量加入方法區(線程共享),存的時候直接MyThreadLocal.threadLocal.set(new UserInfo("1001","zhangsan"));內部threadlocal通過當前線程T找到線程的變量threadLocalMap,然后put(key(線程的ThreadLocal實例對象),value(UserInfo對象))存入Map
取:UserInfo user=MyThreadLocal.threadLocal.get();內部也是通過當前線程綁定的threadLocalMap中,get(key(當前線程的ThreadLocal實例對象)),所以只要線程相同且ThreadLocal實例ID相同,取到的值就相同,反之則不同,從而實現不同線程間隔離,同一線程中共享。
3.跟蹤調試
自定義類MyThreadLocal中,threadLocal在MyThreadLocal初始化時就有值,且只初始化一次,與類相關與實例對象無關,所以任何線程進來都是一個值
在ThreadLocal的set方法源碼中打斷點,跟蹤Thread t的結構,t 中的成員變量threadLocals即為存儲Map,map中有table(Entry數組,元素Entry.key 為線程的ThreadLocal實例對象的弱引用referent,Entry.value為存入對象),第63個元素的value就是存入對象,referent為當前線程的引用
注意:線程Id不同時,本地線程中的變量為null,必須自己用自己取
referent的類型為ThreadLocal
第63元素的referent為下圖
4.多線程傳傳參數
先將本地變量取出,再在新線程中傳入,如果要傳token,只需在userInfo中添加一個字段,請求進入時在controller或者攔截器中MyThreadLocal.add(userInfo)即可后續使用。
//先取出當前值
UserInfo userInfo= MyThreadLocal.getUserInfo();
//推薦使用線程池創建新線程
new Thread(()->{ log.info("before setting,"+JSONObject.toJSONString(MyThreadLocal.getUserInfo()));//返回NULL MyThreadLocal.add(sysOperDTO); log.info("after setting,=="+JSONObject.toJSONString(MyThreadLocal.getUserInfo()));//返回對象 }).start();
UserInfo類
@Data public class UserInfo { private String threadName; private String threadId; }
MyThreadLocal
public class MyThreadLocal { private MyThreadLocal() { } private static final ThreadLocal<UserInfo> threadLocalUser = new ThreadLocal<>(); public static void add(UserInfo userInfo) { userInfo.setThreadName(Thread.currentThread().getName()); userInfo.setThreadId("threadId:"+String.valueOf(Thread.currentThread().getId())+" threadLocalUser.hashCode=="+ threadLocalUser.hashCode()); threadLocalUser.set(userInfo); } public static UserInfo getUserInfo() { return threadLocalUser.get(); } public static void remove() { threadLocalUser.remove(); } }
5.Map實際結構,是ThreadLocal的靜態內部類
/** * 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<ThreadLocal<?>> { /** 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) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) 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: {@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. */ 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內存泄漏問題:https://www.cnblogs.com/xzwblog/p/7227509.html
volatile的深入理解:https://www.jianshu.com/p/4901b2fdfce1
線程池的詳解:https://www.cnblogs.com/zzuli/p/9386463.html
對象的強引用弱引用:https://www.cnblogs.com/yanggb/p/10386175.html
關閉線程三種方法:https://www.cnblogs.com/liyutian/p/10196044.html