上週我僥倖經過美團一面,崗位是java後端開發工程師。java
美團面試官給我進行了二面。面試
面試過程當中他問了ThreadLocal原理(上次問線程池,此次問ThreadLocal,美團爸爸這麼喜歡線程安全機制麼),今天詳細講一講ThreadLocal原理。spring
ThreadLocal是線程的內部存儲類,能夠在指定線程內存儲數據。只有指定線程能夠獲得存儲數據。後端
/** * This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread that accesses one (via its * {@code get} or {@code set} method) has its own, independently initialized * copy of the variable. {@code 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). */
每一個線程都有一個ThreadLocalMap的實例對象,而且經過ThreadLocal管理ThreadLocalMap。設計模式
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
每一個新線程都會實例化爲一個ThreadLocalMap而且賦值給成員變量ThreadLocals,使用時若已經存在threadLocals則直接使用已經存在的對象。數組
應用場景安全
當某些數據是以線程爲做用域而且不一樣線程有不一樣數據副本時,考慮ThreadLocal。網絡
無狀態,副本變量獨立後不影響業務邏輯的高併發場景。數據結構
若是若是業務邏輯強依賴於副本變量,則不適合用ThreadLocal解決。併發
get()與set()
set()是調用ThreadLocalMap的set()實現的
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } //getMap方法 ThreadLocalMap getMap(Thread t) { //thred中維護了一個ThreadLocalMap return t.threadLocals; } //createMap void createMap(Thread t, T firstValue) { //實例化一個新的ThreadLocalMap,並賦值給線程的成員變量threadLocals t.threadLocals = new ThreadLocalMap(this, firstValue); }
ThreadLocalMap爲每一個Thread都維護了一個數組table,ThreadLocal肯定了一個數組下標,而這個下標是value存儲的對應位置。
ThreadLocalMaps是延遲構造的,所以只有在至少要放置一個條目時才建立。
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); }
ThreadLocalMap初始化時建立了默認長度是16的Entry數組。經過hashCode與length位運算肯定索引值i。
每一個Thread都有一個ThreadLocalMap類型。至關於每一個線程Thread都有一個Entry型的數組table。而一切讀取過程都是經過操做這個數組table進行的。
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(); //若是存在key則覆蓋 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(); }
將threadLocalHashCode與長度進行位運算獲得索引。
threadLocalHashCode的代碼以下:
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); }
因爲是static變量,threadLocalHashCode在每次加載threadLocal類時會從新初始化,同時會自增一次,增長HASH_INCREMENT(斐波那契散列乘數,經過該數散列出來的結果會比較均勻)。
static變量也稱做靜態變量,靜態變量和非靜態變量的區別是:靜態變量被全部的對象所共享,在內存中只有一個副本,它當且僅當在類初次加載時會被初始化。
而非靜態變量是對象所擁有的,在建立對象的時候被初始化,存在多個副本,各個對象擁有的副本互不影響。static成員變量的初始化順序按照定義的順序進行初始化。
對於一個ThreadLocal來說,他的索引值i是肯定的。對於不一樣線程,同一個threadlocal對應的是不一樣table的同一下標,便是table[i],不一樣線程之間的table是相互獨立的。
get() 方法
計算索引,直接取出
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
remove() 方法
/** * 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; } } }
線程隔離特性
線程隔離特性,只有在線程內才能獲取到對應的值,線程外不能訪問。
(1)Synchronized是經過線程等待,犧牲時間來解決訪問衝突
(1)ThreadLocal是經過每一個線程單獨一份存儲空間,犧牲空間來解決衝突
內存泄露問題
存在內存泄露問題,每次使用完ThreadLocal,都調用它的remove()方法,清除數據。
Demo程序
import java.util.concurrent.atomic.AtomicInteger; /** * <h3>Exper1</h3> * <p>ThreadLocalId</p> * * @author : cxc * @date : 2020-04-01 23:48 **/ public class ThreadLocalId { // 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(); } public static void remove() { threadId.remove(); } } /** * <h3>Exper1</h3> * <p></p> * * @author : cxc * @date : 2020-04-02 00:07 **/ public class ThreadLocalMain { private static void incrementSameThreadId(){ try{ for(int i=0;i<5;i++){ System.out.println(Thread.currentThread() +"_"+i+",threadId:"+ ThreadLocalId.get()); } }finally { ThreadLocalId.remove(); } } public static void main(String[] args) { incrementSameThreadId(); new Thread(new Runnable() { @Override public void run() { incrementSameThreadId(); } }).start(); new Thread(new Runnable() { @Override public void run() { incrementSameThreadId(); } }).start(); } }
ThreadLocal的原理在面試中幾乎被問爛了。
Thread的私有數據是存儲在ThreadLocalMap,經過ThreadLoacl進行管理。
要了解ThreadLocal的原理,最好多閱讀幾遍源碼,尤爲是ThreadLocalMap的源碼部分。你們面試前要把知識點記牢。
文源網絡,僅供學習之用,若有侵權,聯繫刪除。我將面試題和答案都整理成了PDF文檔,還有一套學習資料,涵蓋Java虛擬機、spring框架、Java線程、數據結構、設計模式等等,但不只限於此。
關注公衆號【java圈子】獲取資料,還有優質文章每日送達。