深入剖析ThreadLocal線程局部變量

1.簡介

多線程訪問同一個共享變量的時候容易出現併發問題,特別是多個線程對一個變量進行寫入的時候,爲了保證線程安全,一般使用者在訪問共享變量的時候需要進行額外的同步措施才能保證線程安全性。ThreadLocal是除了加鎖這種同步方式之外的一種保證一種規避多線程訪問出現線程不安全的方法,當我們在創建一個變量後,如果每個線程對其進行訪問的時候訪問的都是線程自己的變量這樣就不會存在線程不安全問題。

2.每個線程的變量副本是存儲在哪裏的

Thread類中提供了這樣兩個變量可以使每個線程有屬於自己本身線程的變量而不被其他線程共享
在這裏插入圖片描述
我們發現二者都是ThreadLocal內部類ThreadLocalMap類型的變量

3.通過源碼分析ThreadLocal具體實怎樣工作的

ThreadLocal提供了set和get訪問器用來訪問與當前線程相關聯的線程局部變量,

3.1set方法

public void set(T value) {
    //(1)獲取當前線程(調用者線程)
    Thread t = Thread.currentThread();
    //(2)以當前線程作爲key值,去查找對應的線程變量,找到對應的map
    ThreadLocalMap map = getMap(t);
    //(3)如果map不爲null,就直接添加本地變量
    if (map != null)
        map.set(this, value);
    //(4)如果map爲null,說明首次添加,需要首先創建出對應的map
    else
        createMap(t, value);
}

getMap方法:

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals; //獲取線程自己的變量threadLocals,並綁定到當前調用線程的成員變量threadLocals上
}

createMap方法:

void createMap(Thread t, T firstValue) {
     t.threadLocals = new ThreadLocalMap(this, firstValue); 
}

我們通過分析set方法源碼:某個線程通過threadLocal變量設置值的時候會獲取自己線程的threadLocals變量即getMap(t),通過
ThreadLocalMap 類型來接收,並且通過set(this,value)將threadLocal作爲鍵,value當作值存儲在本地線程,到現在爲止我們就可以得出下面的結論了:

  • 每個線程的本地變量不是存放在ThreadLocal實例中,而是放在調用線程的ThreadLocals變量裏面

3.2get方法

我們接着來看get方法

public T get() {
    //(1)獲取當前線程
    Thread t = Thread.currentThread();
    //(2)獲取當前線程的threadLocals變量
    ThreadLocalMap map = getMap(t);
    //(3)如果threadLocals變量不爲null,就可以在map中查找到本地變量的值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //(4)執行到此處,threadLocals爲null,調用該更改初始化當前線程的threadLocals變量
    return setInitialValue();
}

private T setInitialValue() {
    //protected T initialValue() {return null;}
    T value = initialValue();
    //獲取當前線程
    Thread t = Thread.currentThread();
    //以當前線程作爲key值,去查找對應的線程變量,找到對應的map
    ThreadLocalMap map = getMap(t);
    //如果map不爲null,就直接添加本地變量,key爲當前線程,值爲添加的本地變量值
    if (map != null)
        map.set(this, value);
    //如果map爲null,說明首次添加,需要首先創建出對應的map
    else
        createMap(t, value);
    return value;
}

在get方法的實現中,首先獲取當前調用者線程,如果當前線程的threadLocals不爲null,就直接返回當前線程綁定的本地變量值,以threadLocal作爲參數,取出value,否則執行setInitialValue方法初始化threadLocals變量。在setInitialValue方法中,類似於set方法的實現,都是判斷當前線程的threadLocals變量是否爲null,是則添加本地變量(這個時候由於是初始化,所以添加的值爲null),否則創建threadLocals變量,同樣添加的值爲null。

4.變量副本【每個線程中保存的那個map中的變量】是怎麼聲明和初始化的

從我們分析set和get方法的時候其實已經得到了這個答案,當線程中的threadLocals成員是null的時候,會調用ThreadLocal.createMap(Thread t, T firstValue)創建一個map。同時根據函數參數設置上初始值。也就是說,當前線程的threadlocalmap是在第一次調用set的時候創建map並且設置上相應的值的。
在每個線程中,都維護了一個threadlocals對象,在沒有ThreadLocal變量的時候是null的。一旦在ThreadLocal的createMap函數中初始化之後,這個threadlocals就初始化了。以後每次ThreadLocal對象想要訪問變量的時候,比如set函數和get函數,都是先通過getMap(Thread t)函數,先將線程的map取出,然後再從這個在線程(Thread)中維護的map中取出數據或者存入對應數據。

5.不同的線程局部變量,比如說聲明瞭n個(n>=2)這樣的線程局部變量threadlocal,那麼在Thread中的threadlocals中是怎麼存儲的呢?threadlocalmap中是怎麼操作的?

我們先來一個Demo:

public class ThreadLocalTest {

    static ThreadLocal<String> localVar = new ThreadLocal<>();

    static void print(String str) {
        //打印當前線程中本地內存中本地變量的值
        System.out.println(str + " :" + localVar.get());
        //清除本地內存中的本地變量
        localVar.remove();
    }

    public static void main(String[] args) {
        Thread t1  = new Thread(new Runnable() {
            @Override
            public void run() {
                //設置線程1中本地變量的值
                localVar.set("localVar1");
                //調用打印方法
                print("thread1");
                //打印本地變量
                System.out.println("after remove : " + localVar.get());
            }
        });

        Thread t2  = new Thread(new Runnable() {
            @Override
            public void run() {
                //設置線程1中本地變量的值
                localVar.set("localVar2");
                //調用打印方法
                print("thread2");
                //打印本地變量
                System.out.println("after remove : " + localVar.get());
            }
        });

        t1.start();
        t2.start();
    }
}

這兩個線程都是通過localVar這個ThreadLocal來往本地線程中完成副本的創建和賦值,並且在set函數中,map.set(this, value)把當前的localVar傳入到map中作爲鍵,也就是說,在不同的線程的threadlocals變量中,都會有一個以你所聲明的那個線程局部變量threadlocal作爲鍵的key-value。假設說聲明瞭N個這樣的線程局部變量變量,那麼在線程的ThreadLocalMap中就會有n個分別以你的線程局部變量作爲key的鍵值對。

6.總結

在往本地線程設置線程局部變量時,ThreadLocal更像是一個輔助類,這個類本身不含有ThreadLocalMap threadLocals變量,而是通過set或get方法進而操作調用它的線程的threadLocals變量
在這裏插入圖片描述
參考博客:
https://www.cnblogs.com/fsmly/p/11020641.html

https://blog.csdn.net/qq_33404395/article/details/82356344

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章