excel求和與計算器求和相差0.01(excel求和0.00)
899
2022-05-30
ThreadLocal主要是提供線程內部的局部變量,在每個線程內隨時隨地可取,隔離其他線程。
1. ThreadLocal接口
1.1 ThreadLocal類接口很簡單,只有4個方法,我們先來了解一下:
void set(Object value)設置當前線程的線程局部變量的值。
public Object get()該方法返回當前線程所對應的線程局部變量。
public void remove()將當前線程局部變量的值刪除,目的是為了減少內存的占用,該方法是JDK 5.0新增的方法。需要指出的是,當線程結束后,對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量并不是必須的操作,但它可以加快內存回收的速度。
protected Object initialValue()返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,并且僅執行1次。ThreadLocal中的缺省實現直接返回一個null。
在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析什么時候對變量進行讀寫,什么時候需要鎖定某個對象,什么時候釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。
而ThreadLocal則從另一個角度來解決多線程的并發訪問。ThreadLocal會為每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal。
如果想在get之前不需要調用set就能正常訪問的話,必須重寫initialValue()方法。最常見的ThreadLocal使用場景為 用來解決 數據庫連接、Session管理等。
1.2 使用ThreadLocal
public class ThreadTest { ThreadLocal
以上demo覆寫了initialValue()方法,或者調用set方法,否則會報空指針異常。在main線程中和thread1線程中,longLocal保存的副本值和stringLocal保存的副本值都不一樣。
2. ThreadLocalMap
ThreadLocalMap的Entry繼承了WeakReference,并且使用ThreadLocal作為鍵值。
1. 實際的通過ThreadLocal創建的副本是存儲在每個線程自己的threadLocals中的;
2. 為何threadLocals的類型ThreadLocalMap的鍵值為ThreadLocal對象,因為每個線程中可有多個threadLocal變量,就像上面代碼中的longLocal和stringLocal;
3. 在進行get之前,必須先set,否則會報空指針異常;
2.1 方法分析
1). JDK8的ThreadLocal的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) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
2). getMap
/** * 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; }
3). 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; }
get方法的流程是這樣的:
1.首先獲取當前線程
2.根據當前線程獲取一個Map
3.如果獲取的Map不為空,則在Map中以ThreadLocal的引用作為key來在Map中獲取對應的value e,否則轉到5
4.如果e不為null,則返回e.value,否則轉到5
5.Map為空或者e為空,則通過initialValue函數獲取初始值value,然后用ThreadLocal的引用和value作為firstKey和firstValue創建一個新的Map
每個Thread維護一個ThreadLocalMap映射表,這個映射表的key是ThreadLocal實例本身,value是真正需要存儲的Object。
3. WeakReference
關于內存泄露:
ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個ThreadLocal沒有外部強引用引用他,那么系統gc的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當前線程再遲遲不結束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:
ThreadLocal Ref -> Thread -> ThreaLocalMap -> Entry -> value
主要看下getEntryAfterMiss函數:
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; }
ThreadLocalMap的getEntry函數的流程:
首先從ThreadLocal的直接索引位置(通過ThreadLocal.threadLocalHashCode & (len-1)運算得到)獲取Entry e,如果e不為null并且key相同則返回e;
如果e為null或者key不一致則向下一個位置查詢,如果下一個位置的key和當前需要查詢的key相等,則返回對應的Entry,否則,如果key值為null,則擦除該位置的Entry,否則繼續向下一個位置查詢
在這個過程中遇到的key為null的Entry都會被擦除,那么Entry內的value也就沒有強引用鏈,自然會被回收。仔細研究代碼可以發現,set操作也有類似的思想,將key為null的這些Entry都刪除,防止內存泄露。 但是光這樣還是不夠的,上面的設計思路依賴一個前提條件:要調用ThreadLocalMap的genEntry函數或者set函數。這當然是不可能任何情況都成立的,所以很多情況下需要使用者手動調用ThreadLocal的remove函數,手動刪除不再需要的ThreadLocal,防止內存泄露。所以JDK建議將ThreadLocal變量定義成private static的,這樣的話ThreadLocal的生命周期就更長,由于一直存在ThreadLocal的強引用,所以ThreadLocal也就不會被回收,也就能保證任何時候都能根據ThreadLocal的弱引用訪問到Entry的value值,然后remove它,防止內存泄露。
參考:
ThreadLocal和synchronized的區別
Java并發編程:深入剖析ThreadLocal
Java 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。