溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

ThreadLocal的原理和用法

發(fā)布時(shí)間:2021-07-07 16:37:57 來(lái)源:億速云 閱讀:159 作者:chen 欄目:大數(shù)據(jù)

本篇內(nèi)容介紹了“ThreadLocal的原理和用法”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

ThreadLocal簡(jiǎn)介

從名稱看,ThreadLocal 也就是thread和local的組合,也就是一個(gè)thread有一個(gè)local的變量副本 ThreadLocal提供了線程的本地副本,也就是說(shuō)每個(gè)線程將會(huì)擁有一個(gè)自己獨(dú)立的變量副本

方法簡(jiǎn)潔干練,類信息以及方法列表如下:

ThreadLocal的原理和用法

示例

在測(cè)試類中定義了一個(gè)ThreadLocal變量,用于保存String類型數(shù)據(jù) 創(chuàng)建了兩個(gè)線程,分別設(shè)置值,讀取值,移除后再次讀取

package com.declan.threadlocal;

/**
 * @author Declan
 * @date 2019/08/16 14:36
 */
public class ThreadLocalDemo {

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

    public static void main(String[] args) {

        Thread thread1 = new Thread(()-> {
            //thread1中設(shè)置值
            threadLocal.set("this is thread1's local");
            //獲取值
            System.out.println(Thread.currentThread().getName()+": threadLocal value:" + threadLocal.get());
            //移除值
            threadLocal.remove();
            //再次獲取
            System.out.println(Thread.currentThread().getName()+": after remove threadLocal value:" + threadLocal.get());
        }, "thread1");

        Thread thread2 = new Thread(() -> {
            //thread1中設(shè)置值
            threadLocal.set("this is thread2's local");
            //獲取值
            System.out.println(Thread.currentThread().getName()+": threadLocal value:" + threadLocal.get());
            //移除值
            threadLocal.remove();
            //再次獲取
            System.out.println(Thread.currentThread().getName()+": after remove threadLocal value:" + threadLocal.get());

        }, "thread2");

        //啟動(dòng)兩個(gè)線程
        thread1.start();
        thread2.start();
    }
}

結(jié)果

thread2: threadLocal value:this is thread2's local
thread2: after remove threadLocal value:null
thread1: threadLocal value:this is thread1's local
thread1: after remove threadLocal value:null

從結(jié)果可以看得到,每個(gè)線程中可以有自己獨(dú)有的一份數(shù)據(jù),互相沒(méi)有影響remove之后,數(shù)據(jù)被清空

從上面示例也可以看出來(lái)一個(gè)情況:

如果兩個(gè)線程同時(shí)對(duì)一個(gè)變量進(jìn)行操作,互相之間是沒(méi)有影響的,換句話說(shuō),這很顯然并不是用來(lái)解決共享變量的一些并發(fā)問(wèn)題,比如多線程的協(xié)作

因?yàn)門hreadLocal的設(shè)計(jì)理念就是共享變私有,都已經(jīng)私有了,還談啥共享? 比如之前的消息隊(duì)列,生產(chǎn)者消費(fèi)者的示例中final LinkedList<Message> messageQueue = new LinkedList<>();如果這個(gè)LinkedList是ThreadLocal的,生產(chǎn)者使用一個(gè),消費(fèi)者使用一個(gè),還協(xié)作什么呢?

但是共享變私有,如同并發(fā)變串行,或許適合解決一些場(chǎng)景的線程安全問(wèn)題,因?yàn)榭雌饋?lái)就如同沒(méi)有共享變量了,不共享即安全,但是他并不是為了解決線程安全問(wèn)題而存在的

實(shí)例分析

Thread中有一個(gè)threadLocals變量,類型為ThreadLocal.ThreadLocalMap

ThreadLocal的原理和用法

ThreadLocalMap則是ThreadLocal的靜態(tài)內(nèi)部類,他是一個(gè)設(shè)計(jì)用來(lái)保存thread local 變量的自定義的hash map

ThreadLocal的原理和用法

也就是說(shuō)在Thread中有一個(gè)“hashMap”可以用來(lái)保存鍵值對(duì)。

set方法

ThreadLocal的原理和用法

在這個(gè)方法中,接受參數(shù),類型為T的value

首先獲取當(dāng)前線程,然后調(diào)用getMap(t)

ThreadLocal的原理和用法

這個(gè)方法很簡(jiǎn)單,就是直接返回Thread內(nèi)部的那個(gè)“hashMap”(threadLocals是默認(rèn)的訪問(wèn)權(quán)限)

繼續(xù)回到set方法,如果這個(gè)map不為空,那么以this為key,value為值,也就是ThreadLocal變量作為key

如果map為空,那么進(jìn)行給這個(gè)線程創(chuàng)建一個(gè)map ,并且將第一組值設(shè)置進(jìn)去,key仍舊是這個(gè)ThreadLocal變量

ThreadLocal的原理和用法

簡(jiǎn)言之:

調(diào)用一個(gè)ThreadLocal的set方法,會(huì)將:以這個(gè)ThreadLocal類型的變量為key,參數(shù)為value的這一個(gè)鍵值對(duì),保存在Thread內(nèi)部的一個(gè)“hashMap”中

get方法

在get方法內(nèi)部仍舊是獲取當(dāng)前線程的內(nèi)部的這個(gè)“hashMap”,然后以當(dāng)前對(duì)象this(ThreadLocal)作為key,進(jìn)行值的獲取:

ThreadLocal的原理和用法

我們對(duì)這兩個(gè)方法換一個(gè)思路理解:

每個(gè)線程可能運(yùn)行過(guò)程中,可能會(huì)操作很多的ThreadLocal變量,那么怎么區(qū)分各自?

直觀的理解就是,我們想要獲取某個(gè)線程的某個(gè)ThreadLocal變量的值

一個(gè)很好的解決方法就是借助于Map進(jìn)行保存,ThreadLocal變量作為key,local值作為value

假設(shè)這個(gè)map名為:threadLocalsMap,可以提供setter和getter方法進(jìn)行設(shè)置和讀取,內(nèi)部為:

  • threadLocalsMap.set(ThreadLocal key,T value)

  • threadLocalsMap.get(ThreadLocal key)

這樣就可以達(dá)到thread --- local的效果,但是是否存在一些使用不便?我們內(nèi)部定義的是ThreadLocal變量,但是只是用來(lái)作為key的?是否直接通過(guò)ThreadLocal進(jìn)行值的獲取更加方便呢?

怎么能夠做到數(shù)據(jù)讀取的倒置?因?yàn)楫吘怪档拇_是保存在Thread中的

其實(shí)也很簡(jiǎn)單,只需要內(nèi)部進(jìn)行轉(zhuǎn)換就好了,對(duì)于下面兩個(gè)方法,我們都需要 ThreadLocal 作為key

  • threadLocalsMap.set(ThreadLocal key,T value)

  • threadLocalsMap.get(ThreadLocal key)

而這個(gè)key不就是這個(gè)ThreadLocal,不就是this 么

所以:

  • ThreadLocal.set(T value)就內(nèi)部調(diào)用threadLocalsMap.set(this,T value)

  • ThreadLocal.get()就內(nèi)部調(diào)用threadLocalsMap.get(this)

所以總結(jié)下就是:

  • 每個(gè)Thread內(nèi)部保存了一個(gè)"hashMap",key為ThreadLocal,這個(gè)線程操作了多少個(gè)ThreadLocal,就有多少個(gè)key

  • 你想獲取一個(gè)ThreadLocal變量的值,就是ThreadLocal.get(),內(nèi)部就是hashMap.get(this);

  • 你想設(shè)置一個(gè)ThreadLocal變量的值,就是ThreadLocal.set(T value),內(nèi)部就是hashMap.set(this,value);

關(guān)鍵只是在于內(nèi)部的這個(gè)“hashMap”,ThreadLocal只是讀寫倒置的“殼”,可以更簡(jiǎn)潔易用的通過(guò)這個(gè)殼進(jìn)行變量的讀寫,“倒置”的紐帶,就是getMap(t)方法.

remove方法

ThreadLocal的原理和用法

remove方法很簡(jiǎn)單,當(dāng)前線程,獲取當(dāng)前線程的hashMap,remove

初始值

再次回頭看下get方法,如果第一次調(diào)用時(shí),指定線程并沒(méi)有threadLocals,或者根本都沒(méi)有進(jìn)行過(guò)set,會(huì)發(fā)生什么?

如下圖所示,會(huì)調(diào)用setInitialValue方法:

ThreadLocal的原理和用法

在setInitialValue方法中,會(huì)調(diào)用initialValue方法獲取初始值,如果該線程沒(méi)有threadLocals那么會(huì)創(chuàng)建,如果有,會(huì)使用這個(gè)初始值構(gòu)造這個(gè)ThreadLocal的鍵值對(duì),簡(jiǎn)單說(shuō),如果沒(méi)有set過(guò)(或者壓根內(nèi)部的這個(gè)threadLocals就是null的),那么她返回的值就是初始值

ThreadLocal的原理和用法

這個(gè)內(nèi)部的initialValue方法默認(rèn)的返回null,所以一個(gè)ThreadLocal如果沒(méi)有進(jìn)行set操作,那么初始值為null:

ThreadLocal的原理和用法

如何進(jìn)行初始值的設(shè)定?

可以看得出來(lái),這是一個(gè)protected方法,所以返回一個(gè)覆蓋了這個(gè)方法的子類不就好了?在子類中實(shí)現(xiàn)初始值的設(shè)置。

總結(jié)

通過(guò)set方法可以進(jìn)行值的設(shè)定

通過(guò)get方法可以進(jìn)行值的讀取,如果沒(méi)有進(jìn)行過(guò)設(shè)置,那么將會(huì)返回null;如果使用了withInitial方法提供了初始值,將會(huì)返回初始值

通過(guò)remove方法將會(huì)移除對(duì)該值的寫入,再次調(diào)用get方法,如果使用了withInitial方法提供了初始值,將會(huì)返回初始值,否則返回null

對(duì)于get方法,很顯然如果沒(méi)有提供初始值,返回值為null,在使用時(shí)是需要注意不要引起NPE異常

ThreadLocal,thread local,每個(gè)線程一份,到底是什么意思?

他的意思是對(duì)于一個(gè)ThreadLocal類型變量,每個(gè)線程有一個(gè)對(duì)應(yīng)的值,這個(gè)值的名字就是ThreadLocal類型變量的名字,值是我們set進(jìn)去的變量, 但是如果set設(shè)置的是共享變量,那么ThreadLocal其實(shí)本質(zhì)上還是同一個(gè)對(duì)象不是么?

這句話如果有疑問(wèn)的話,可以這么理解:

對(duì)于同一個(gè)ThreadLocal變量a,每個(gè)線程有一個(gè)map,map中都有一個(gè)鍵值對(duì),key為a,值為你保存的值,但是這個(gè)值,到底每個(gè)線程都是全新的?還是使用的同一個(gè)?這是你自己的問(wèn)題了?。?!

ThreadLocal可以做到每個(gè)線程有一個(gè)獨(dú)立的一份值,但是你非得使用共享變量將他們?cè)O(shè)置成一個(gè),那ThreadLocal是不會(huì)保障的。 這就好比一個(gè)對(duì)象,有很多引用指向他,每個(gè)線程有一個(gè)獨(dú)立的引用,但是對(duì)象根本還是只有一個(gè)。所以,從這個(gè)角度更容易理解,為什么說(shuō)ThreadLocal并不是為了解決線程安全問(wèn)題而設(shè)計(jì)的,因?yàn)樗⒉粫?huì)為線程安全做什么保障,他的能力是持有多個(gè)引用,這多個(gè)引用是否能保障是多個(gè)不同的對(duì)象,你來(lái)決策!

所以我們最開(kāi)始說(shuō)的,ThreadLocal會(huì)為每個(gè)線程創(chuàng)建一個(gè)變量副本的說(shuō)法是不嚴(yán)謹(jǐn)?shù)模?是他有這個(gè)能力做到這件事情,但是到底是什么對(duì)象,還是要看你set的是什么,set本身不會(huì)對(duì)你的值進(jìn)行干涉

應(yīng)用場(chǎng)景

前面說(shuō)過(guò),對(duì)于之前生產(chǎn)者消費(fèi)者的示例中,就不適合使用ThreadLocal,因?yàn)閱?wèn)題模型就是要多線程之間協(xié)作,而不是為了線程安全就將共享變量私有化

比如,銀行賬戶的存款和取款,如果借助于ThreadLocal創(chuàng)建了兩個(gè)賬戶對(duì)象,就會(huì)有問(wèn)題的,初始值500,明明又存進(jìn)來(lái)1000塊,可支配的總額還是500

那ThreadLocal適合什么場(chǎng)景呢? 既然是每個(gè)線程一個(gè),自然是適合那種希望每個(gè)線程擁有一個(gè)的那種場(chǎng)景(好像是廢話)

一個(gè)線程中一個(gè),也就是線程隔離,既然是一個(gè)線程一個(gè),那么同一個(gè)線程中調(diào)用的方法也就是共享了,所以說(shuō),有時(shí),ThreadLocal會(huì)被用來(lái)作為參數(shù)傳遞的工具

因?yàn)樗軌虮U贤粋€(gè)線程中的值是唯一的,那么他就共享于所有的方法中,對(duì)于所有的方法來(lái)說(shuō),相當(dāng)于一個(gè)全局變量了!

所以可以用來(lái)同一個(gè)線程內(nèi)全局參數(shù)傳遞

示例

對(duì)于JavaWeb項(xiàng)目,大家都了解過(guò)Session

ps:此處不對(duì)session展開(kāi)介紹,打開(kāi)瀏覽器輸入網(wǎng)址,這就會(huì)建立一個(gè)session,關(guān)閉瀏覽器,session就失效了

在這個(gè)時(shí)間段內(nèi),一個(gè)用戶的多個(gè)請(qǐng)求中,共享同一個(gè)session,Session 保存了很多信息,有的需要通過(guò) Session 獲取信息,有些又需要修改 Session 的信息。

每個(gè)線程需要獨(dú)立的session,而且很多地方都需要操作 Session,存在多方法共享 Session 的需求,所以session對(duì)象需要在多個(gè)方法中共享

如果不使用 ThreadLocal,可以在每個(gè)線程內(nèi)創(chuàng)建一個(gè) Session對(duì)象,然后在多個(gè)方法中將他作為參數(shù)進(jìn)行傳遞

很顯然,如果每次都顯式的傳遞參數(shù),繁瑣易錯(cuò)

這種場(chǎng)景就適合使用ThreadLocal

下面的示例就模擬了多方法共享同一個(gè)session,但是線程間session隔離的示例:

package com.declan.threadlocal;


/**
 * @author Declan
 * @date 2019/08/16 16:07
 */
public class SessionDemo {

    /**
     * session變量定義
     */
    static ThreadLocal<Session> sessionThreadLocal = new ThreadLocal<Session>();


    /**
     * 獲取session
     * @return
     */
    public static Session getSession(){
        if(sessionThreadLocal.get() == null){
            sessionThreadLocal.set(new Session());
        }
        return sessionThreadLocal.get();
    }

    /**
     * 移除session
     */
    public static void closeSession(){
        sessionThreadLocal.remove();
    }

    /**
     * 模擬一個(gè)調(diào)用session的方法1
     */
    public static void fun1(Session session){

    }

    /**
     * 模擬一個(gè)調(diào)用session的方法2
     */
    public static void fun2(Session session){

    }

    /**
     * 模擬session對(duì)象
     */
    static class Session{

    }

    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            fun1(sessionThreadLocal.get());
            fun2(sessionThreadLocal.get());
            closeSession();
        });
        thread.start();
    }
}

所以,ThreadLocal最根本的使用場(chǎng)景應(yīng)該是:

在每個(gè)線程希望有一個(gè)獨(dú)有的變量時(shí)(這個(gè)變量還很可能需要在同一個(gè)線程內(nèi)共享),避免每個(gè)線程還需要主動(dòng)地去創(chuàng)建這個(gè)對(duì)象(如果還需要共享,也一并解決了參數(shù)來(lái)回傳遞的問(wèn)題), 換句話說(shuō)就是,“如何優(yōu)雅的解決:線程間隔離與線程內(nèi)共享的問(wèn)題”,而不是說(shuō)用來(lái)解決亂七八糟的線程安全問(wèn)題。

所以說(shuō)如果有些場(chǎng)景你需要線程隔離,那么考慮ThreadLocal,而不是你有了什么線程安全問(wèn)題需要解決,然后求助于ThreadLocal,這不是一回事

再次注意:

ThreadLocal只是具有這樣的能力,是你能夠做到每個(gè)線程一個(gè)獨(dú)有變量,但是如果你set時(shí),不是傳遞的new出來(lái)的新變量,也就只是理解成“每個(gè)線程不同的引用”,對(duì)象還是那個(gè)對(duì)象(有點(diǎn)像參數(shù)傳遞時(shí)的值傳遞,對(duì)于對(duì)象傳遞的就是引用)

總結(jié)

ThreadLocal可以用來(lái)優(yōu)雅的解決線程間隔離的對(duì)象,必須主動(dòng)創(chuàng)建的問(wèn)題,借助于ThreadLocal無(wú)需在線程中顯式的創(chuàng)建對(duì)象,解決方案很優(yōu)雅

ThreadLocal中的set方法并不會(huì)保障的確是每個(gè)線程會(huì)獲得不同的對(duì)象,你需要對(duì)邏輯進(jìn)行一定的處理(比如上面的示例中的getSession方法,如果ThreadLocal 變量的get為null,那么new對(duì)象) 是否真的能夠做到線程隔離,還要看你自己的編碼實(shí)現(xiàn),不過(guò)如果是共享變量,你還放到ThreadLocal中干嘛?

所以通常都是線程獨(dú)有的對(duì)象,通過(guò)new創(chuàng)建。

“ThreadLocal的原理和用法”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI