Android多線程:帶你了解神秘的線程變量ThreadLocal


前言

  • Java多線程中,線程變量ThreadLocal非常重要,但對于很多開發(fā)者來說,這并不容易理解,甚至覺得有點神秘
  • 今天,我將獻上一份 ThreadLocal的介紹 & 實戰(zhàn)攻略,希望你們會喜歡。

Carson帶你學多線程系列
基礎匯總
Android多線程:基礎知識匯總
基礎使用
Android多線程:繼承Thread類使用(含實例教程)
Android多線程:實現(xiàn)Runnable接口使用(含實例教程)
復合使用
Android多線程:AsyncTask使用教程(含實例講解)
Android多線程:AsyncTask原理及源碼分析
Android多線程:HandlerThread使用教程(含實例講解)
Android多線程:HandlerThread原理及源碼分析
Android多線程:IntentService使用教程(含實例講解)
Android多線程:IntentService的原理及源碼分析
Android多線程:線程池ThreadPool全方位教學
相關使用
Android異步通信:這是一份全面&詳細的Handler機制學習攻略
Android多線程:手把手教你全面學習神秘的Synchronized關鍵字
Android多線程:帶你了解神秘的線程變量 ThreadLocal


目錄

示意圖

1. 簡介

示意圖

2. 使用流程

主要是創(chuàng)建ThreadLocal變量 & 訪問ThreadLocal變量

2.1 創(chuàng)建ThreadLocal變量

共有3種方式,具體如下

// 1. 直接創(chuàng)建對象
private ThreadLocal myThreadLocal = new ThreadLocal()

// 2. 創(chuàng)建泛型對象
private ThreadLocal myThreadLocal = new ThreadLocal<String>();

// 3. 創(chuàng)建泛型對象 & 初始化值
// 指定泛型的好處:不需要每次對使用get()方法返回的值作強制類型轉換
private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
    @Override
    protected String initialValue() {
        return "This is the initial value";
    }
};

// 特別注意:
// 1. ThreadLocal實例 = 類中的private、static字段
// 2. 只需實例化對象一次 & 不需知道它是被哪個線程實例化
// 3. 每個線程都保持 對其線程局部變量副本 的隱式引用
// 4. 線程消失后,其線程局部實例的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)
// 5. 雖然所有的線程都能訪問到這個ThreadLocal實例,但是每個線程只能訪問到自己通過調用ThreadLocal的set()設置的值
 // 即 哪怕2個不同的線程在同一個`ThreadLocal`對象上設置了不同的值,他們仍然無法訪問到對方的值

2.2 訪問ThreadLocal變量

// 1. 設置值:set()
// 需要傳入一個Object類型的參數(shù)
myThreadLocal.set("初始值”);

// 2. 讀取ThreadLocal變量中的值:get()
// 返回一個Object對象
String threadLocalValue = (String) myThreadLocal.get();

3. 具體使用

以下則是測試代碼

 public class ThreadLocalTest {

        // 測試代碼
        public static void main(String[] args){
            // 新開2個線程用于設置 & 獲取 ThreadLoacl的值
            MyRunnable runnable = new MyRunnable();
            new Thread(runnable, "線程1").start();
            new Thread(runnable, "線程2").start();
        }

        // 線程類
        public static class MyRunnable implements Runnable {

            // 創(chuàng)建ThreadLocal & 初始化
            private ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
                @Override
                protected String initialValue() {
                    return "初始化值";
                }
            };

            @Override
            public void run() {

                // 運行線程時,分別設置 & 獲取 ThreadLoacl的值
                String name = Thread.currentThread().getName();
                threadLocal.set(name + "的threadLocal"); // 設置值 = 線程名
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name + ":" + threadLocal.get());
            }
        }
    }
  • 測試結果
線程1:線程1的threadLocal
線程2:線程2的threadLocal

// 從上述結果看出,在2個線程分別設置ThreadLocal值 & 分別獲取,結果并未互相干擾

4. 實現(xiàn)原理

  • 核心原理
    ThreadLocal類中有1個Map(稱:ThreadLocalMap):用于存儲每個線程 & 該線程設置的存儲在ThreadLocal變量的值
  1. ThreadLocalMap的鍵Key = 當前ThreadLocal實例、值value = 該線程設置的存儲在ThreadLocal變量的值
  2. keyThreadLocal對象的弱引用;當要拋棄掉ThreadLocal對象時,垃圾收集器會忽略該 key的引用而清理掉ThreadLocal對象
  • 關于如何設置 & 獲取 ThreadLocal變量里的值,具體請看下面的源碼分析

請直接看代碼注釋


// ThreadLocal的源碼

public class ThreadLocal<T> {

    ...

  /** 
    * 設置ThreadLocal變量引用的值
    *  ThreadLocal變量引用 指向 ThreadLocalMap對象,即設置ThreadLocalMap的值 = 該線程設置的存儲在ThreadLocal變量的值
    *  ThreadLocalMap的鍵Key = 當前ThreadLocal實例
    *  ThreadLocalMap的值 = 該線程設置的存儲在ThreadLocal變量的值
    **/  
    public void set(T value) {
      
        // 1. 獲得當前線程
        Thread t = Thread.currentThread();

        // 2. 獲取該線程的ThreadLocalMap對象 ->>分析1
        ThreadLocalMap map = getMap(t);

        // 3. 若該線程的ThreadLocalMap對象已存在,則替換該Map里的值;否則創(chuàng)建1個ThreadLocalMap對象
        if (map != null)
            map.set(this, value);// 替換
        else
            createMap(t, value);// 創(chuàng)建->>分析2
    }

  /** 
    * 獲取ThreadLocal變量里的值
    * 由于ThreadLocal變量引用 指向 ThreadLocalMap對象,即獲取ThreadLocalMap對象的值 = 該線程設置的存儲在ThreadLocal變量的值
    **/ 
    public T get() {

        // 1. 獲得當前線程
        Thread t = Thread.currentThread();

        // 2. 獲取該線程的ThreadLocalMap對象
        ThreadLocalMap map = getMap(t);

        // 3. 若該線程的ThreadLocalMap對象已存在,則直接獲取該Map里的值;否則則通過初始化函數(shù)創(chuàng)建1個ThreadLocalMap對象
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value; // 直接獲取值
        }
        return setInitialValue(); // 初始化
    }

  /** 
    * 初始化ThreadLocal的值
    **/ 
    private T setInitialValue() {

        T value = initialValue();

        // 1. 獲得當前線程
        Thread t = Thread.currentThread();

        // 2. 獲取該線程的ThreadLocalMap對象
        ThreadLocalMap map = getMap(t);

         // 3. 若該線程的ThreadLocalMap對象已存在,則直接替換該值;否則則創(chuàng)建
        if (map != null)
            map.set(this, value); // 替換
        else
            createMap(t, value); // 創(chuàng)建->>分析2
        return value;
    }


  /** 
    * 分析1:獲取當前線程的threadLocals變量引用
    **/ 
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

  /** 
    * 分析2:創(chuàng)建當前線程的ThreadLocalMap對象
    **/ 
    void createMap(Thread t, T firstValue) {
    // 新創(chuàng)建1個ThreadLocalMap對象 放入到 Thread類的threadLocals變量引用中:
        // a. ThreadLocalMap的鍵Key = 當前ThreadLocal實例
        // b. ThreadLocalMap的值 = 該線程設置的存儲在ThreadLocal變量的值
        t.threadLocals = new ThreadLocalMap(this, firstValue);
        // 即 threadLocals變量 屬于 Thread類中 ->> 分析3
    }

  
    ...
}

  /** 
    * 分析3:Thread類 源碼分析
    **/ 

    public class Thread implements Runnable {
       ...

       ThreadLocal.ThreadLocalMap threadLocals = null;
       // 即 Thread類持有threadLocals變量
       // 線程類實例化后,每個線程對象擁有獨立的threadLocals變量變量
       // threadLocals變量在 ThreadLocal對象中 通過set() 或 get()進行操作

       ...
}



5. 額外補充

5.1 ThreadLocal如何做到線程安全

  • 每個線程擁有自己獨立的ThreadLocals變量(指向ThreadLocalMap對象 )
  • 每當線程 訪問 ThreadLocals變量時,訪問的都是各自線程自己的ThreadLocalMap變量(鍵 - 值)
  • ThreadLocalMap變量的鍵 key = 唯一 = 當前ThreadLocal實例

上述3點 保證了線程間的數(shù)據(jù)訪問隔離,即線程安全

  • 測試代碼
 public class ThreadLocalTest {

        // 測試代碼
        public static void main(String[] args){
            // 新開2個線程用于設置 & 獲取 ThreadLoacl的值
            MyRunnable runnable = new MyRunnable();
            new Thread(runnable, "線程1").start();
            new Thread(runnable, "線程2").start();
        }

        // 線程類
        public static class MyRunnable implements Runnable {

            // 創(chuàng)建ThreadLocal & 初始化
            private ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
                @Override
                protected String initialValue() {
                    return "初始化值";
                }
            };

            @Override
            public void run() {

                // 運行線程時,分別設置 & 獲取 ThreadLoacl的值
                String name = Thread.currentThread().getName();
                threadLocal.set(name + "的threadLocal"); // 設置值 = 線程名
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name + ":" + threadLocal.get());
            }
        }
    }
  • 測試結果
線程1:線程1的threadLocal
線程2:線程2的threadLocal

// 從上述結果看出,在2個線程分別設置ThreadLocal值 & 分別獲取,結果并未互相干擾

5.2 與同步機制的區(qū)別

示意圖

6. 總結

  • 本文全面講解了Java多線程ThreadLocal的相關知識
  • 下一篇文章我將對講解Android多線程的相關知識,感興趣的同學可以繼續(xù)關注Carson_Ho的簡書

Carson帶你學多線程系列
基礎匯總
Android多線程:基礎知識匯總
基礎使用
Android多線程:繼承Thread類使用(含實例教程)
Android多線程:實現(xiàn)Runnable接口使用(含實例教程)
復合使用
Android多線程:AsyncTask使用教程(含實例講解)
Android多線程:AsyncTask原理及源碼分析
Android多線程:HandlerThread使用教程(含實例講解)
Android多線程:HandlerThread原理及源碼分析
Android多線程:IntentService使用教程(含實例講解)
Android多線程:IntentService的原理及源碼分析
Android多線程:線程池ThreadPool全方位教學
相關使用
Android異步通信:這是一份全面&詳細的Handler機制學習攻略
Android多線程:手把手教你全面學習神秘的Synchronized關鍵字
Android多線程:帶你了解神秘的線程變量 ThreadLocal


歡迎關注Carson_Ho的簡書

不定期分享關于安卓開發(fā)的干貨,追求短、平、快,但卻不缺深度。


請點贊!因為你的鼓勵是我寫作的最大動力!

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容