Java | ThreadLocal 用法解析【Deprecated】

提示 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é),希望能幫上忙

系列文章

延伸文章

ThreadLocal 思維導(dǎo)圖

目錄


1. ThreadLocal API

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

ThradlLocal UML類圖

現(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è)置初始值
    1. set()設(shè)置當(dāng)前線程ThreadLocal變量的值
    • 不同線程設(shè)置的值互不干擾,不會相互覆蓋
    1. remove()移除當(dāng)前線程之前設(shè)置在ThreadLocal變量上的值
    • 如果在當(dāng)前線程下次調(diào)用get()之前,還沒有調(diào)用set()設(shè)置新值,則依舊會調(diào)用setInitialValue()重新設(shè)置初始值。
    1. initialValue()子類重寫此方法可以定義ThreadLocal變量的初始值
    • 默認(rèn)的初始值為null

2. 生命周期

總結(jié)一下ThreadLocal的生命周期,如下圖所示:

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)了空間換時間的思想。

Timethreads圖 - 01


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代替CalendarDateTimeFormatter代替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)作不易,你的「三連」是丑丑最大的動力,我們下次見!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容