
提示 2021年1月2日
這篇文章是2019年寫的,寫的不好。今年我重新梳理了一遍,你可以直接看:「Java 路線」| ThreadLocal
點贊關(guān)注,不再迷路,你的支持對我意義重大!
?? Hi,我是丑丑。這里有 Android 進(jìn)階成長路線筆記 & 博客,歡迎跟著彭丑丑一起成長。(聯(lián)系方式在 GitHub)
前言
- ThreadLocal是一種無同步的線程安全實現(xiàn),體現(xiàn)了
Thread-Specific Storage模式:即使只有一個入口,內(nèi)部也會為每個線程分配特有的存儲空間。由于線程間沒有共享資源,因此可以實現(xiàn)無鎖線程安全; - 這篇文章將總結(jié)
ThreadLocal的用法 & 實現(xiàn)細(xì)節(jié),希望能幫上忙
系列文章
延伸文章

目錄

1. ThreadLocal API
ThreadLocal的用法很簡單,ThreadLocal提供了下列的public與protected方法:

現(xiàn)在我們查看ThreadLocal中與上述幾個方法有關(guān)的代碼,簡化代碼如下:
// ThreadLocal.java
// ThreadLocal構(gòu)造方法里什么都沒做
public ThreadLocal() {
// do nothing
}
// 定義ThreadLocal變量的初始值
protected T initialValue() {
// 默認(rèn)的初始值為null
return null;
}
// 內(nèi)部方法:用于設(shè)置當(dāng)前線程里ThreadLocal變量初始值
private T setInitialValue() {
T value = initialValue();
// 其實ThreadLocal的源碼并不是直接調(diào)用set(),但源碼中這部分代碼
// 就相當(dāng)于調(diào)用set()方法,這是為了防止子類重寫set()造成異常
set(value);
return value;
}
// 獲取當(dāng)前線程中ThreadLocal變量的值
public T get() {
Thread t = Thread.currentThread();
// ThreadLocalMap是什么?稍后介紹
ThreadLocalMap map = getMap(t);
if (map != null) {
// 存在匹配的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
// 變量的值不為null,返回
T result = (T)e.value;
return result;
}
}
// 獲取的值為空,設(shè)置變量的初始值并返回
return setInitialValue();
}
// 設(shè)置當(dāng)前線程中ThreadLocal變量的值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// ThreadLocalMap懶初始化,直到設(shè)置值的時候才創(chuàng)建
createMap(t, value);
}
// 移除當(dāng)前線程中ThreadLocal變量的值
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap存儲在Thread的屬性中,簡化代碼如下:
// Thread.java
ThreadLocal.ThreadLocalMap threadLocals = null;
// 線程退出之前,會置空threadLocals變量,以便隨后GC
private void exit() {
// ...
threadLocals = null;
// ...
}
分析代碼,可以總結(jié)出方法的用法:
- 1.
get()獲取當(dāng)前線程中ThreadLocal變量的值- 不同線程獲取的值互不干擾
- 如果取值為null,則調(diào)用
initialValue()設(shè)置初始值
-
-
set()設(shè)置當(dāng)前線程ThreadLocal變量的值
- 不同線程設(shè)置的值互不干擾,不會相互覆蓋
-
-
-
remove()移除當(dāng)前線程之前設(shè)置在ThreadLocal變量上的值
- 如果在當(dāng)前線程下次調(diào)用
get()之前,還沒有調(diào)用set()設(shè)置新值,則依舊會調(diào)用setInitialValue()重新設(shè)置初始值。
-
-
-
initialValue()子類重寫此方法可以定義ThreadLocal變量的初始值
- 默認(rèn)的初始值為null
-
2. 生命周期
總結(jié)一下ThreadLocal的生命周期,如下圖所示:

3. 使用案例
我們直接以android.os.Looper.java 中使用ThreadLocal的源碼作為例子:
/frameworks/base/core/java/android/os/Looper.java
public class Looper {
// ...
// 靜態(tài)ThreadLocal變量,所有類實例共享同一個ThreadLocal變量
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");
}
// 設(shè)置ThreadLocal變量的值
sThreadLocal.set(new Looper(quitAllowed));
}
public static Looper myLooper() {
// 獲取ThreadLocal變量的值
return sThreadLocal.get();
}
public static void prepare() {
prepare(true);
}
// ...
}
-
ThreadLocal被聲明為static final變量,泛型參數(shù)為Looper,表示ThreadLocal變量接受Looper類型的值 -
prepare()中調(diào)用ThreadLocal#set()設(shè)置當(dāng)前線程的Looper -
myLooper()中調(diào)用ThreadLocal#get()獲取當(dāng)前線程的Looper
我們可以畫出Looper中訪問ThreadLocal的Timethreads圖,如下圖所示,不同線程獨占一個Looper變量,線程間不存在共享資源??梢钥吹?code>ThreadLocal實現(xiàn)了無鎖線程安全,避免了加解鎖造成的上下文切換,體現(xiàn)了空間換時間的思想。

4. 編程規(guī)約
記得嗎?《阿里巴巴Java開發(fā)手冊》中提到過關(guān)于ThreadLocal的編程規(guī)約,如下所示:
-
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的應(yīng)用,可以使用
Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable thread-safe. -
15.【參考】(原文過于啰嗦,以下為筆者轉(zhuǎn)述)
ThreadLocal變量建議使用static修飾,可以保證變量在類初始化時創(chuàng)建,所有類實例可以共享同一個靜態(tài)變量。注意到了嗎?在文章開頭的Looper.java源碼中,
ThreadLocal變量就是使用static修飾的
5. 使用場景
-
以空間換時間實現(xiàn)無鎖線程安全
ThreadLocal相對于Synchronized等互斥鎖避免了上下文切換損耗,有助于提高吞吐量 -
線程級別的單例模式
一般的單例對象是對整個進(jìn)程可見的,假如這個對象不是線程安全的(比如SimpleDateFormat),就可以很方便的使用
ThreadLocal實現(xiàn)線程級別的單例,保證線程安全 -
共享參數(shù)
如果一個模塊有非常多地方需要使用同一個變量,相比于在每個方法中重復(fù)傳遞同一個參數(shù),使用
ThreadLocal作為一個全局變量也許是另一種選擇方式。
看到這里,相信你已經(jīng)掌握了ThreadLocal的用法,下一篇文章將深入ThreadLocal的核心,探討數(shù)據(jù)結(jié)構(gòu)ThreadLocalMap的實現(xiàn)細(xì)節(jié),歡迎關(guān)注彭旭銳的簡書!
參考
- ThreadLocal.java — Josh Bloch and Doug Lea
- 《深入理解Java虛擬機 — JVM高級特性與最佳實踐》 周志明 著
- 《Java并發(fā)編程的藝術(shù)》 方騰飛 魏鵬 程曉明 著
- 《數(shù)據(jù)結(jié)構(gòu)與算法分析 — Java語言描述》 [美]Mark Allen Weiss 著
- 《阿里巴巴Java開發(fā)手冊》 楊冠寶 編著
創(chuàng)作不易,你的「三連」是丑丑最大的動力,我們下次見!
