溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

ThreadLocal的原理和用法

發布時間:2021-07-07 16:37:57 來源:億速云 閱讀:215 作者:chen 欄目:大數據

本篇內容介紹了“ThreadLocal的原理和用法”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

ThreadLocal簡介

從名稱看,ThreadLocal 也就是thread和local的組合,也就是一個thread有一個local的變量副本 ThreadLocal提供了線程的本地副本,也就是說每個線程將會擁有一個自己獨立的變量副本

方法簡潔干練,類信息以及方法列表如下:

ThreadLocal的原理和用法

示例

在測試類中定義了一個ThreadLocal變量,用于保存String類型數據 創建了兩個線程,分別設置值,讀取值,移除后再次讀取

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中設置值
            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中設置值
            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");

        //啟動兩個線程
        thread1.start();
        thread2.start();
    }
}

結果

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

從結果可以看得到,每個線程中可以有自己獨有的一份數據,互相沒有影響remove之后,數據被清空

從上面示例也可以看出來一個情況:

如果兩個線程同時對一個變量進行操作,互相之間是沒有影響的,換句話說,這很顯然并不是用來解決共享變量的一些并發問題,比如多線程的協作

因為ThreadLocal的設計理念就是共享變私有,都已經私有了,還談啥共享? 比如之前的消息隊列,生產者消費者的示例中final LinkedList<Message> messageQueue = new LinkedList<>();如果這個LinkedList是ThreadLocal的,生產者使用一個,消費者使用一個,還協作什么呢?

但是共享變私有,如同并發變串行,或許適合解決一些場景的線程安全問題,因為看起來就如同沒有共享變量了,不共享即安全,但是他并不是為了解決線程安全問題而存在的

實例分析

Thread中有一個threadLocals變量,類型為ThreadLocal.ThreadLocalMap

ThreadLocal的原理和用法

ThreadLocalMap則是ThreadLocal的靜態內部類,他是一個設計用來保存thread local 變量的自定義的hash map

ThreadLocal的原理和用法

也就是說在Thread中有一個“hashMap”可以用來保存鍵值對。

set方法

ThreadLocal的原理和用法

在這個方法中,接受參數,類型為T的value

首先獲取當前線程,然后調用getMap(t)

ThreadLocal的原理和用法

這個方法很簡單,就是直接返回Thread內部的那個“hashMap”(threadLocals是默認的訪問權限)

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

如果map為空,那么進行給這個線程創建一個map ,并且將第一組值設置進去,key仍舊是這個ThreadLocal變量

ThreadLocal的原理和用法

簡言之:

調用一個ThreadLocal的set方法,會將:以這個ThreadLocal類型的變量為key,參數為value的這一個鍵值對,保存在Thread內部的一個“hashMap”中

get方法

在get方法內部仍舊是獲取當前線程的內部的這個“hashMap”,然后以當前對象this(ThreadLocal)作為key,進行值的獲取:

ThreadLocal的原理和用法

我們對這兩個方法換一個思路理解:

每個線程可能運行過程中,可能會操作很多的ThreadLocal變量,那么怎么區分各自?

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

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

假設這個map名為:threadLocalsMap,可以提供setter和getter方法進行設置和讀取,內部為:

  • threadLocalsMap.set(ThreadLocal key,T value)

  • threadLocalsMap.get(ThreadLocal key)

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

怎么能夠做到數據讀取的倒置?因為畢竟值的確是保存在Thread中的

其實也很簡單,只需要內部進行轉換就好了,對于下面兩個方法,我們都需要 ThreadLocal 作為key

  • threadLocalsMap.set(ThreadLocal key,T value)

  • threadLocalsMap.get(ThreadLocal key)

而這個key不就是這個ThreadLocal,不就是this 么

所以:

  • ThreadLocal.set(T value)就內部調用threadLocalsMap.set(this,T value)

  • ThreadLocal.get()就內部調用threadLocalsMap.get(this)

所以總結下就是:

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

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

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

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

remove方法

ThreadLocal的原理和用法

remove方法很簡單,當前線程,獲取當前線程的hashMap,remove

初始值

再次回頭看下get方法,如果第一次調用時,指定線程并沒有threadLocals,或者根本都沒有進行過set,會發生什么?

如下圖所示,會調用setInitialValue方法:

ThreadLocal的原理和用法

在setInitialValue方法中,會調用initialValue方法獲取初始值,如果該線程沒有threadLocals那么會創建,如果有,會使用這個初始值構造這個ThreadLocal的鍵值對,簡單說,如果沒有set過(或者壓根內部的這個threadLocals就是null的),那么她返回的值就是初始值

ThreadLocal的原理和用法

這個內部的initialValue方法默認的返回null,所以一個ThreadLocal如果沒有進行set操作,那么初始值為null:

ThreadLocal的原理和用法

如何進行初始值的設定?

可以看得出來,這是一個protected方法,所以返回一個覆蓋了這個方法的子類不就好了?在子類中實現初始值的設置。

總結

通過set方法可以進行值的設定

通過get方法可以進行值的讀取,如果沒有進行過設置,那么將會返回null;如果使用了withInitial方法提供了初始值,將會返回初始值

通過remove方法將會移除對該值的寫入,再次調用get方法,如果使用了withInitial方法提供了初始值,將會返回初始值,否則返回null

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

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

他的意思是對于一個ThreadLocal類型變量,每個線程有一個對應的值,這個值的名字就是ThreadLocal類型變量的名字,值是我們set進去的變量, 但是如果set設置的是共享變量,那么ThreadLocal其實本質上還是同一個對象不是么?

這句話如果有疑問的話,可以這么理解:

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

ThreadLocal可以做到每個線程有一個獨立的一份值,但是你非得使用共享變量將他們設置成一個,那ThreadLocal是不會保障的。 這就好比一個對象,有很多引用指向他,每個線程有一個獨立的引用,但是對象根本還是只有一個。所以,從這個角度更容易理解,為什么說ThreadLocal并不是為了解決線程安全問題而設計的,因為他并不會為線程安全做什么保障,他的能力是持有多個引用,這多個引用是否能保障是多個不同的對象,你來決策!

所以我們最開始說的,ThreadLocal會為每個線程創建一個變量副本的說法是不嚴謹的, 是他有這個能力做到這件事情,但是到底是什么對象,還是要看你set的是什么,set本身不會對你的值進行干涉

應用場景

前面說過,對于之前生產者消費者的示例中,就不適合使用ThreadLocal,因為問題模型就是要多線程之間協作,而不是為了線程安全就將共享變量私有化

比如,銀行賬戶的存款和取款,如果借助于ThreadLocal創建了兩個賬戶對象,就會有問題的,初始值500,明明又存進來1000塊,可支配的總額還是500

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

一個線程中一個,也就是線程隔離,既然是一個線程一個,那么同一個線程中調用的方法也就是共享了,所以說,有時,ThreadLocal會被用來作為參數傳遞的工具

因為它能夠保障同一個線程中的值是唯一的,那么他就共享于所有的方法中,對于所有的方法來說,相當于一個全局變量了!

所以可以用來同一個線程內全局參數傳遞

示例

對于JavaWeb項目,大家都了解過Session

ps:此處不對session展開介紹,打開瀏覽器輸入網址,這就會建立一個session,關閉瀏覽器,session就失效了

在這個時間段內,一個用戶的多個請求中,共享同一個session,Session 保存了很多信息,有的需要通過 Session 獲取信息,有些又需要修改 Session 的信息。

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

如果不使用 ThreadLocal,可以在每個線程內創建一個 Session對象,然后在多個方法中將他作為參數進行傳遞

很顯然,如果每次都顯式的傳遞參數,繁瑣易錯

這種場景就適合使用ThreadLocal

下面的示例就模擬了多方法共享同一個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();
    }

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

    }

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

    }

    /**
     * 模擬session對象
     */
    static class Session{

    }

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

所以,ThreadLocal最根本的使用場景應該是:

在每個線程希望有一個獨有的變量時(這個變量還很可能需要在同一個線程內共享),避免每個線程還需要主動地去創建這個對象(如果還需要共享,也一并解決了參數來回傳遞的問題), 換句話說就是,“如何優雅的解決:線程間隔離與線程內共享的問題”,而不是說用來解決亂七八糟的線程安全問題。

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

再次注意:

ThreadLocal只是具有這樣的能力,是你能夠做到每個線程一個獨有變量,但是如果你set時,不是傳遞的new出來的新變量,也就只是理解成“每個線程不同的引用”,對象還是那個對象(有點像參數傳遞時的值傳遞,對于對象傳遞的就是引用)

總結

ThreadLocal可以用來優雅的解決線程間隔離的對象,必須主動創建的問題,借助于ThreadLocal無需在線程中顯式的創建對象,解決方案很優雅

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

所以通常都是線程獨有的對象,通過new創建。

“ThreadLocal的原理和用法”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女