目錄
定義
ThreadLocal是線程局部變量,不同線程的threadlocal相互獨立。它是一種保存線程私有信息的機制,因爲在現成的整個生命週期都有效,
所以可以方便地在一個線程關聯的不同業務模塊之間傳遞信息,比如事務ID、Cookie等上下文相關信息。
特點:
- 簡單,開箱即用。
- 快速,無額外開銷。
安全,線程安全。
threadlocal設計實現的優點:開箱即用,代碼易讀,符合經常閱讀偶爾使用的原則
場景:資源持有、線程一致性、併發計算等多線程場景。
API
場景分析
如jdbc事務的請求,part i 代表不同事務,如更新用戶信息、更新訂單信息等,需要保證各事務資源的一致性。
每一個事務請求連接時,先去threadlocal map裏找,如果不存在,到連接池中請求分配連接,並存入map。
總結,ThreadLocal的主要作用是:
- 持有線程資源,供線程的各個部分使用
- 幫助維護多線程共享資源的一致性
- 線程安全的一種方案
場景實驗,觀察Spring框架在多線程場景的執行情況
壓力測試工具,apache2-util
10000此請求,單線程
10000次請求,線程數加到100
雖然完成10000請求的速度變快了,但最終curl請求get到的數據會不一致。
原因是,多線程下,c是臨界資源,c=c+1不具備原子性,要先讀取c,在執行加1,再執行賦值。
對c的訪問加鎖
測試發現curl get到的結果是10000,但速度會很慢,原因是加鎖導致排隊,併發實質上變成了串行,性能被鎖卡住。
解決方法就是使用ThreadLocal,讓線程在自己的局部變量資源上運行。
把c設爲ThreadLocal
測試發現,最終統計數據依然不準確。
原因是spring默認線程池有20多個線程,這些線程每一個都有自己的局部變量c,執行10000請求後,需要收集各個線程的數據。
收集多個ThreadLocal中的數據
雖然threadlocal是各個線程獨佔的數據,但也是進程持有的,不過java沒有提供收集數據的接口,所以可以通過hashmap或
hashset來存儲threadlocal,最後一併收集。
改進,因爲set訪問需要同步,所以addset中加入同步鎖,而且set訪問次數最多是線程池的線程數,相對c的訪問次數要少,
屬於低頻訪問,所以對總體性能影響小。
實驗總結
- 基於線程池模型加同步鎖很危險,可能因排隊等鎖,導致cpu使用不充分,從而嚴重拖慢性能。
- 雖然多線程不能完全避免同步問題,但使用ThreadLocal,可以把高頻同步化爲低頻同步。
實現原理
自定義HashMap存放ThreadLocal弱引用,延續了lazy-load模式,初始容量16,門限2/3
satic class ThreadLocalMap {
satic class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// …
}
回收被內存回收的弱引用所佔的槽是超級複雜問題。當Key爲null時,該條目就變成“廢棄條目”,
相關“value”的回收,往往依賴於幾個關鍵點,即set、remove、rehash。
下面set的精簡代碼,具體的清理邏輯是實現在cleanSomeSlots和expungeStaleEntry之中。
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];; …) {
//…
if (k == null) {
// 替換廢棄條目
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
// 掃描並清理髮現的廢棄條目,並檢查容量是否超限
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();// 清理廢棄條目,如果仍然超限,則擴容(加倍)
}
廢棄項目的回收依賴於顯式地觸發,否則就要等待線程結束,進而回收相應ThreadLocalMap!這就是很多OOM的來源,
所以應用一定要自己負 責remove,並且不要和線程池配合,因爲worker線程往往是不會退出的。
hash算法
散列更均勻,減少衝突
解決衝突的方法是後移法。
總結
解決一致性問題,除了排隊(加鎖)、投票(拜占庭將軍)、CAS+voilate外,ThreadLocal不失爲一個更輕量級的優選方案。