當(dāng)前位置:首頁 > 公眾號(hào)精選 > 架構(gòu)師社區(qū)
[導(dǎo)讀]來自:冰河技術(shù) ? ? ? 前言 我們都知道,在多線程環(huán)境下訪問同一個(gè)共享變量,可能會(huì)出現(xiàn)線程安全的問題,為了保證線程安全,我們往往會(huì)在訪問這個(gè)共享變量的時(shí)候加鎖,以達(dá)到同步的效果,如下圖所示。 對(duì)共享變量加鎖雖然能夠保證線程的安全,但是卻增加了開

【高并發(fā)】ThreadLocal學(xué)會(huì)了這些,你也能和面試官扯皮了!

來自:冰河技術(shù)

     

前言

我們都知道,在多線程環(huán)境下訪問同一個(gè)共享變量,可能會(huì)出現(xiàn)線程安全的問題,為了保證線程安全,我們往往會(huì)在訪問這個(gè)共享變量的時(shí)候加鎖,以達(dá)到同步的效果,如下圖所示。

【高并發(fā)】ThreadLocal學(xué)會(huì)了這些,你也能和面試官扯皮了!

對(duì)共享變量加鎖雖然能夠保證線程的安全,但是卻增加了開發(fā)人員對(duì)鎖的使用技能,如果鎖使用不當(dāng),則會(huì)導(dǎo)致死鎖的問題。而ThreadLocal能夠做到在創(chuàng)建變量后,每個(gè)線程對(duì)變量訪問時(shí)訪問的是線程自己的本地變量

什么是ThreadLocal?

ThreadLocal是JDK提供的,支持線程本地變量。也就是說,如果我們創(chuàng)建了一個(gè)ThreadLocal變量,則訪問這個(gè)變量的每個(gè)線程都會(huì)有這個(gè)變量的一個(gè)本地副本。如果多個(gè)線程同時(shí)對(duì)這個(gè)變量進(jìn)行讀寫操作時(shí),實(shí)際上操作的是線程自己本地內(nèi)存中的變量,從而避免了線程安全的問題。

【高并發(fā)】ThreadLocal學(xué)會(huì)了這些,你也能和面試官扯皮了!

ThreadLocal使用示例

例如,我們使用ThreadLocal保存并打印相關(guān)的變量信息,程序如下所示。

public class ThreadLocalTest {

    private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

    public static void main(String[] args){
        //創(chuàng)建第一個(gè)線程
        Thread threadA = new Thread(()->{
            threadLocal.set("ThreadA:" + Thread.currentThread().getName());
            System.out.println("線程A本地變量中的值為:" + threadLocal.get());
        });
        //創(chuàng)建第二個(gè)線程
        Thread threadB = new Thread(()->{
            threadLocal.set("ThreadB:" + Thread.currentThread().getName());
            System.out.println("線程B本地變量中的值為:" + threadLocal.get());
        });
        //啟動(dòng)線程A和線程B
        threadA.start();
        threadB.start();
    }
}

運(yùn)行程序,打印的結(jié)果信息如下所示。

線程A本地變量中的值為:ThreadA:Thread-0
線程B本地變量中的值為:ThreadB:Thread-1

此時(shí),我們?yōu)榫€程A增加刪除ThreadLocal中的變量的操作,如下所示。

public class ThreadLocalTest {

    private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

    public static void main(String[] args){
        //創(chuàng)建第一個(gè)線程
        Thread threadA = new Thread(()->{
            threadLocal.set("ThreadA:" + Thread.currentThread().getName());
            System.out.println("線程A本地變量中的值為:" + threadLocal.get());
            threadLocal.remove();
            System.out.println("線程A刪除本地變量后ThreadLocal中的值為:" + threadLocal.get());
        });
        //創(chuàng)建第二個(gè)線程
        Thread threadB = new Thread(()->{
            threadLocal.set("ThreadB:" + Thread.currentThread().getName());
            System.out.println("線程B本地變量中的值為:" + threadLocal.get());
            System.out.println("線程B沒有刪除本地變量:" + threadLocal.get());
        });
        //啟動(dòng)線程A和線程B
        threadA.start();
        threadB.start();
    }
}

此時(shí)的運(yùn)行結(jié)果如下所示。

線程A本地變量中的值為:ThreadA:Thread-0
線程B本地變量中的值為:ThreadB:Thread-1
線程B沒有刪除本地變量:ThreadB:Thread-1
線程A刪除本地變量后ThreadLocal中的值為:null

通過上述程序我們可以看出,線程A和線程B存儲(chǔ)在ThreadLocal中的變量互不干擾,線程A存儲(chǔ)的變量只能由線程A訪問,線程B存儲(chǔ)的變量只能由線程B訪問。

【高并發(fā)】ThreadLocal學(xué)會(huì)了這些,你也能和面試官扯皮了!

ThreadLocal原理

首先,我們看下Thread類的源碼,如下所示。

public class Thread implements Runnable {
    /***********省略N行代碼*************/
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
     /***********省略N行代碼*************/
}

由Thread類的源碼可以看出,在ThreadLocal類中存在成員變量threadLocals和inheritableThreadLocals,這兩個(gè)成員變量都是ThreadLocalMap類型的變量,而且二者的初始值都為null。只有當(dāng)前線程第一次調(diào)用ThreadLocal的set()方法或者get()方法時(shí)才會(huì)實(shí)例化變量。

這里需要注意的是:每個(gè)線程的本地變量不是存放在ThreadLocal實(shí)例里面的,而是存放在調(diào)用線程的threadLocals變量里面的。也就是說,調(diào)用ThreadLocal的set()方法存儲(chǔ)的本地變量是存放在具體線程的內(nèi)存空間中的,而ThreadLocal類只是提供了set()和get()方法來存儲(chǔ)和讀取本地變量的值,當(dāng)調(diào)用ThreadLocal類的set()方法時(shí),把要存儲(chǔ)的值放入調(diào)用線程的threadLocals中存儲(chǔ)起來,當(dāng)調(diào)用ThreadLocal類的get()方法時(shí),從當(dāng)前線程的threadLocals變量中將存儲(chǔ)的值取出來。

接下來,我們分析下ThreadLocal類的set()、get()和remove()方法的實(shí)現(xiàn)邏輯。

set()方法

set()方法的源代碼如下所示。

public void set(T value) {
    //獲取當(dāng)前線程
    Thread t = Thread.currentThread();
    //以當(dāng)前線程為Key,獲取ThreadLocalMap對(duì)象
    ThreadLocalMap map = getMap(t);
    //獲取的ThreadLocalMap對(duì)象不為空
    if (map != null)
        //設(shè)置value的值
        map.set(this, value);
    else
        //獲取的ThreadLocalMap對(duì)象為空,創(chuàng)建Thread類中的threadLocals變量
        createMap(t, value);
}

在set()方法中,首先獲取調(diào)用set()方法的線程,接下來,使用當(dāng)前線程作為Key調(diào)用getMap(t)方法來獲取ThreadLocalMap對(duì)象,getMap(Thread t)的方法源碼如下所示。

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

可以看到,getMap(Thread t)方法獲取的是線程變量自身的threadLocals成員變量。

在set()方法中,如果調(diào)用getMap(t)方法返回的對(duì)象不為空,則把value值設(shè)置到Thread類的threadLocals成員變量中,而傳遞的key為當(dāng)前ThreadLocal的this對(duì)象,value就是通過set()方法傳遞的值。

如果調(diào)用getMap(t)方法返回的對(duì)象為空,則程序調(diào)用createMap(t, value)方法來實(shí)例化Thread類的threadLocals成員變量。

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

也就是創(chuàng)建當(dāng)前線程的threadLocals變量。

get()方法

get()方法的源代碼如下所示。

public T get() {
    //獲取當(dāng)前線程
    Thread t = Thread.currentThread();
    //獲取當(dāng)前線程的threadLocals成員變量
    ThreadLocalMap map = getMap(t);
    //獲取的threadLocals變量不為空
    if (map != null) {
        //返回本地變量對(duì)應(yīng)的值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //初始化threadLocals成員變量的值
    return setInitialValue();
}

通過當(dāng)前線程來獲取threadLocals成員變量,如果threadLocals成員變量不為空,則直接返回當(dāng)前線程綁定的本地變量,否則調(diào)用setInitialValue()方法初始化threadLocals成員變量的值。

private T setInitialValue() {
    //調(diào)用初始化Value的方法
    T value = initialValue();
    Thread t = Thread.currentThread();
    //根據(jù)當(dāng)前線程獲取threadLocals成員變量
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //threadLocals不為空,則設(shè)置value值
        map.set(this, value);
    else
        //threadLocals為空,創(chuàng)建threadLocals變量
        createMap(t, value);
    return value;
}

其中,initialValue()方法的源碼如下所示。

protected T initialValue() {
    return null;
}

通過initialValue()方法的源碼可以看出,這個(gè)方法可以由子類覆寫,在ThreadLocal類中,這個(gè)方法直接返回null。

remove()方法

remove()方法的源代碼如下所示。

public void remove() {
    //根據(jù)當(dāng)前線程獲取threadLocals成員變量
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        //threadLocals成員變量不為空,則移除value值
        m.remove(this);
}

remove()方法的實(shí)現(xiàn)比較簡(jiǎn)單,首先根據(jù)當(dāng)前線程獲取threadLocals成員變量,不為空,則直接移除value的值。

注意:如果調(diào)用線程一致不終止,則本地變量會(huì)一直存放在調(diào)用線程的threadLocals成員變量中,所以,如果不需要使用本地變量時(shí),可以通過調(diào)用ThreadLocal的remove()方法,將本地變量從當(dāng)前線程的threadLocals成員變量中刪除,以免出現(xiàn)內(nèi)存溢出的問題。

【高并發(fā)】ThreadLocal學(xué)會(huì)了這些,你也能和面試官扯皮了!

ThreadLocal變量不具有傳遞性

使用ThreadLocal存儲(chǔ)本地變量不具有傳遞性,也就是說,同一個(gè)ThreadLocal在父線程中設(shè)置值后,在子線程中是無法獲取到這個(gè)值的,這個(gè)現(xiàn)象說明ThreadLocal中存儲(chǔ)的本地變量不具有傳遞性。

接下來,我們來看一段代碼,如下所示。

public class ThreadLocalTest {

    private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

    public static void main(String[] args){
        //在主線程中設(shè)置值
        threadLocal.set("ThreadLocalTest");
        //在子線程中獲取值
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子線程獲取值:" + threadLocal.get());
            }
        });
        //啟動(dòng)子線程
        thread.start();
        //在主線程中獲取值
        System.out.println("主線程獲取值:" + threadLocal.get());
    }
}

運(yùn)行這段代碼輸出的結(jié)果信息如下所示。

主線程獲取值:ThreadLocalTest
子線程獲取值:null

通過上述程序,我們可以看出在主線程中向ThreadLocal設(shè)置值后,在子線程中是無法獲取到這個(gè)值的。那有沒有辦法在子線程中獲取到主線程設(shè)置的值呢?此時(shí),我們可以使用InheritableThreadLocal來解決這個(gè)問題。

InheritableThreadLocal使用示例

InheritableThreadLocal類繼承自ThreadLocal類,它能夠讓子線程訪問到在父線程中設(shè)置的本地變量的值,例如,我們將ThreadLocalTest類中的threadLocal靜態(tài)變量改寫成InheritableThreadLocal類的實(shí)例,如下所示。

public class ThreadLocalTest {

    private static ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>();

    public static void main(String[] args){
        //在主線程中設(shè)置值
        threadLocal.set("ThreadLocalTest");
        //在子線程中獲取值
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子線程獲取值:" + threadLocal.get());
            }
        });
        //啟動(dòng)子線程
        thread.start();
        //在主線程中獲取值
        System.out.println("主線程獲取值:" + threadLocal.get());
    }
}

此時(shí),運(yùn)行程序輸出的結(jié)果信息如下所示。

主線程獲取值:ThreadLocalTest
子線程獲取值:ThreadLocalTest

可以看到,使用InheritableThreadLocal類存儲(chǔ)本地變量時(shí),子線程能夠獲取到父線程中設(shè)置的本地變量。

【高并發(fā)】ThreadLocal學(xué)會(huì)了這些,你也能和面試官扯皮了!

InheritableThreadLocal原理

首先,我們來看下InheritableThreadLocal類的源碼,如下所示。

public class InheritableThreadLocal<Textends ThreadLocal<T{
    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

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

由InheritableThreadLocal類的源代碼可知,InheritableThreadLocal類繼承自ThreadLocal類,并且重寫了ThreadLocal類的childValue()方法、getMap()方法和createMap()方法。也就是說,當(dāng)調(diào)用ThreadLocal的set()方法時(shí),創(chuàng)建的是當(dāng)前Thread線程的inheritableThreadLocals成員變量而不再是threadLocals成員變量。

這里,我們需要思考一個(gè)問題:InheritableThreadLocal類的childValue()方法是何時(shí)被調(diào)用的呢?這就需要我們來看下Thread類的構(gòu)造方法了,如下所示。

public Thread() {
     init(nullnull"Thread-" + nextThreadNum(), 0);
 }

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

Thread(Runnable target, AccessControlContext acc) {
    init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}

public Thread(ThreadGroup group, Runnable target) {
    init(group, target, "Thread-" + nextThreadNum(), 0);
}

public Thread(String name) {
    init(nullnull, name, 0);
}

public Thread(ThreadGroup group, String name) {
    init(group, null, name, 0);
}

public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}

public Thread(ThreadGroup group, Runnable target, String name) {
    init(group, target, name, 0);
}

public Thread(ThreadGroup group, Runnable target, String name,
              long stackSize)
 
{
    init(group, target, name, stackSize);
}

可以看到,Thread類的構(gòu)造方法最終調(diào)用的是init()方法,那我們就來看下init()方法,如下所示。

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals)
 
{
       /************省略部分源碼************/
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

可以看到,在init()方法中會(huì)判斷傳遞的inheritThreadLocals變量是否為true,同時(shí)父線程中的inheritableThreadLocals是否為null,如果傳遞的inheritThreadLocals變量為true,同時(shí),父線程中的inheritableThreadLocals不為null,則調(diào)用ThreadLocal類的createInheritedMap()方法。

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

在createInheritedMap()中,使用父線程的inheritableThreadLocals變量作為參數(shù)創(chuàng)建新的ThreadLocalMap對(duì)象。然后在Thread類的init()方法中會(huì)將這個(gè)ThreadLocalMap對(duì)象賦值給子線程的inheritableThreadLocals成員變量。

接下來,我們來看看ThreadLocalMap的構(gòu)造函數(shù)都干了啥,如下所示。

private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];

    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                //調(diào)用重寫的childValue方法
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

在ThreadLocalMap的構(gòu)造函數(shù)中,調(diào)用了InheritableThreadLocal類重寫的childValue()方法。而InheritableThreadLocal類通過重寫getMap()方法和createMap()方法,讓本地變量保存到了Thread線程的inheritableThreadLocals變量中,線程通過InheritableThreadLocal類的set()方法和get()方法設(shè)置變量時(shí),就會(huì)創(chuàng)建當(dāng)前線程的inheritableThreadLocals變量。此時(shí),如果父線程創(chuàng)建子線程,在Thread類的構(gòu)造函數(shù)中會(huì)把父線程中的inheritableThreadLocals變量里面的本地變量復(fù)制一份保存到子線程的inheritableThreadLocals變量中。

最后,附上并發(fā)編程需要掌握的核心技能知識(shí)圖,祝大家在學(xué)習(xí)并發(fā)編程時(shí),少走彎路。

【高并發(fā)】ThreadLocal學(xué)會(huì)了這些,你也能和面試官扯皮了!

特別推薦一個(gè)分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長(zhǎng)按關(guān)注一下:

【高并發(fā)】ThreadLocal學(xué)會(huì)了這些,你也能和面試官扯皮了!

長(zhǎng)按訂閱更多精彩▼

【高并發(fā)】ThreadLocal學(xué)會(huì)了這些,你也能和面試官扯皮了!

如有收獲,點(diǎn)個(gè)在看,誠摯感謝

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉