前言 ThreadLocal 是一種 無同步 的線程安全實現 體現了 Thread-Specific Storage 模式:即使只有一個入口,內部也會為每個線程分配特有的存儲空間,線程間 沒有共用資源 本文將總結 ThreadLocal 的用法與實現細節,希望能幫上忙 ThreadLocal 思維導 ...
前言
- ThreadLocal 是一種 無同步 的線程安全實現
- 體現了
Thread-Specific Storage
模式:即使只有一個入口,內部也會為每個線程分配特有的存儲空間,線程間 沒有共用資源 - 本文將總結
ThreadLocal
的用法與實現細節,希望能幫上忙
ThreadLocal 思維導圖
線程安全 示意圖
1. 用法
ThreadLocal
的用法很簡單, ThreadLocal
提供了下列的public與protected方法,本文將引用 android.os.Looper.java 為例子講解 ThreadLocal
的用法:
ThradlLocal UML類圖
// /frameworks/base/core/java/android/os/Looper.java
public class Looper {
// ...
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 設置線程局部變數的值
sThreadLocal.set(new Looper(quitAllowed));
}
public static Looper myLooper() {
// 獲取線程局部變數的值
return sThreadLocal.get();
}
public static void prepare() {
prepare(true);
}
// ...
}
ThreadLocal
為 static final變數 ,泛型參數為Looper
,表示接受Looper
類型的值Looper#prepare()
中調用ThreadLocal#set()
設置 變數的值,不同線程設置的值互不幹擾,不會相互覆蓋Looper#myLooper()
中調用ThreadLocal#get()
獲取 變數的值,不同線程獲取的值互不幹擾
Timethreads圖 - 01
-
子類 重寫
ThreadLocal#initialValue()
,可以設置變數的初始值,我們查看相關源碼:public ConcurrentHashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); } public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) throw new IllegalArgumentException(); if (concurrencyLevel > MAX_SEGMENTS) concurrencyLevel = MAX_SEGMENTS; // Find power-of-two sizes best matching arguments int sshift = 0; int ssize = 1; while (ssize < concurrencyLevel) { ++sshift; ssize <<= 1; } this.segmentShift = 32 - sshift;//用於高位,判斷落在哪個Segment this.segmentMask = ssize - 1;//用於取模。之前在hashmap的indexFor方法有提過。2的n次方-1 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; int c = initialCapacity / ssize; if (c * ssize < initialCapacity) ++c; int cap = MIN_SEGMENT_TABLE_CAPACITY; while (cap < c) cap <<= 1; // create segments and segments[0] Segment<K,V> s0 = new Segment<K,V>(loadFactor, (int)(cap * loadFactor), (HashEntry<K,V>[])new HashEntry[cap]);//初始化第一個位置的Segment Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];//初始化Segments UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] this.segments = ss; }
- 在
ThreadLocal#get()
中嘗試獲取變數的值,如果為空則會調用ThreadLocal#setInitialValue()
設置設置初始值 - 懶初始化 :直到第一次調用
get()
才會設置初值 - 預設 :設置初始值為 null
- 在
-
ThreadLocal#remove()
用於 移除 變數之前存儲的值。如果在當前線程下次調用ThreadLocal#get()
時,還沒有set()
新的值,依舊會使用ThreadLocal#setInitialValue()
設置初始值。
到這裡我們就可以總結 ThreadLocal
的生命周期,如下圖所示:
ThreadLocal生命周期 示意圖
2. 編程規約
記得嗎?《阿裡巴巴Java開發手冊》中提到過關於 ThreadLocal
的編程規約,如下所示:
-
5.【強制】
SimpleDateFormate
是線程不安全的類,一般不要定義為 static 變數,如果定義為static,必須加鎖,或者使用DateUtils
工具類正例:
private static final ThreadLocal<DataFormat> df = new ThreadLocal<DateFormat>(){ @Override protected DateFormat initialValue(){ return new SimpleDateFormat("yyyy-MM-dd"); } };
說明:如果是JDK8的應用,可以使用
Instant
代替Date
,LocalDateTime
代替Calendar
,DateTimeFormatter
代替SimpleDateFormat
,官方給出的解釋:simple beautiful strong immutable thread-safe. -
15.【參考】(原文過於啰嗦,以下為筆者轉述)
ThreadLocal
變數建議使用 static 修飾,可以保證變數在類初始化時創建,所有類實例可以共用同一個靜態變數。註意到了嗎?在文章開頭的Looper.java源碼中,
ThreadLocal
變數就是使用static
修飾的
看到這裡,相信你已經掌握了 ThreadLocal
的用法,下一篇文章將深入 ThreadLocal
的內部,探討數據結構的實現細節。
Segment
ConcurrentHashMap是由多個Segment組成的,Segment繼承了ReentrantLock,每次加鎖都是對某個Segment,不會影響其他Segment,達到了鎖分離(也叫分段鎖)的作用。
每個Segment又包含了HashEntry數組,HashEntry是一個鏈表。如下圖所示:
concurrencyLevel:併發數,預設16,直接影響segmentShift和segmentMask的值,以及Segment的初始化數量。Segment初始化的數量,為最接近且大於的辦等於2的N次方的值,比如concurrencyLevel=16,Segment數量為16,concurrencyLevel=17,Segment數量為32。segmentShift的值是這樣的,比如Segment是32,相對於2的5次方,那麼他的值就是32-5,為27,後面無符號右移27位,也就是取高5位的時候,就是0到31的值,此時Segment的下標也是0到31,取模後對應著每個Segment。segmentMask就是2的n次方-1,這邊n是5,用於取模。之前在hashmap的indexFor方法有提過。