Java中ThreadLocal共享變量的使用
一、ThreadLocal
我們知道多線程訪問(wèn)同一個(gè)共享變量時(shí),會(huì)出現(xiàn)線程安全問(wèn)題,為了保證線程安全開(kāi)發(fā)者需要對(duì)共享變量的訪問(wèn)操作進(jìn)行適當(dāng)?shù)耐讲僮鳎缂渔i等同步操作。
除此之外,Java提供了ThreadLocal類,當(dāng)一個(gè)共享變量使用ThreadLocal聲明時(shí),它表明,當(dāng)每個(gè)線程訪問(wèn)共享變量時(shí),會(huì)把共享變量復(fù)制一份到線程的工作內(nèi)存,之后線程對(duì)此共享變量進(jìn)行操作時(shí)操作的都是線程工作內(nèi)存的變量而不是主內(nèi)存中的共享變量,從而不需要加鎖的同步操作實(shí)現(xiàn)避免出現(xiàn)線程安全問(wèn)題。
二、Thread使用代碼示例
public class ThreadLocalTest { private static ThreadLocal<String> variable = new ThreadLocal<>(); // (1) public static void main(String[] args) throws InterruptedException { variable.set(Thread.currentThread().getName()); // (2) // 創(chuàng)建線程一 var thread1 = new Thread(() -> { System.err.println("Thread Name before set: " + Thread.currentThread().getName() + " " + variable.get()); // (3) variable.set(Thread.currentThread().getName()); // (4) System.err.println("Thread Name after set: " + Thread.currentThread().getName() + " " + variable.get()); // (5) }); var thread2 = new Thread(() -> { System.err.println("Thread Name before set: " + Thread.currentThread().getName() + " " + variable.get()); // (6) variable.set(Thread.currentThread().getName()); // (7) System.err.println("Thread Name after set: " + Thread.currentThread().getName() + " " + variable.get()); // (8) }); thread1.start(); // (9) thread2.start(); // (10) Thread.sleep(2000); // (11) System.err.println("main thread: " + variable.get()); // (12) } }
輸出:
Thread2 before set: Thread-1 null
Thread2 after set: Thread-1 Thread-1
Thread1 before set: Thread-0 null
Thread1 after set: Thread-0 Thread-0
main thread: main
示例中我們創(chuàng)建了兩個(gè)線程,每個(gè)線程里都讀取和設(shè)置全局的ThreadLcoal變量:
代碼(1)創(chuàng)建了一個(gè)ThreadLocal共享變量variable,這里其實(shí)設(shè)置的是主線程工作內(nèi)存里的共享變量副本
代碼(2)主線程設(shè)置ThreadLocal變量variable
代碼(3)線程一讀取共享變量variable的值
代碼(4)線程一設(shè)置共享變了variable的值,這里其實(shí)設(shè)置的是線程一工作內(nèi)存里的共享變量副本
代碼(5)線程一再次讀取共享變量variable的值
代碼(6)線程二讀取共享變量variable的值
代碼(7)線程二設(shè)置共享變了variable的值,這里其實(shí)設(shè)置的是線程二工作內(nèi)存里的共享變量副本
代碼(8)線程二再次讀取共享變量variable的值
代碼(9)啟動(dòng)線程一
代碼(10)啟動(dòng)線程二
代碼(11)主線程休眠2秒
代碼(12)主線程讀取共享變量variable的值
從輸出我們可以看到,每個(gè)兩個(gè)線程所操作的ThreadLocal變量互不影響,其實(shí)每個(gè)線程在設(shè)置和讀取共享變量variable時(shí)操作的都是共享變量在線程自己工作內(nèi)存里的副本,并不會(huì)影響到其他線程的值。
三、ThreadLocal原理
我們說(shuō)線程操作ThreadLocal類型的變量時(shí),會(huì)復(fù)制一個(gè)變量副本到線程工作空間,然后所有操作都是對(duì)副本變量進(jìn)行的。那線程是怎么復(fù)制ThreadLocal變量到線程工作空間的,線程和ThreadLocal之前是怎么關(guān)聯(lián)的。首先我們來(lái)看一看Thread的結(jié)構(gòu)
可以看到Thread類有很多屬性,我們現(xiàn)在只關(guān)心threadLocals
和inheritableThreadLocals
,這兩個(gè)變量都是ThreadLocalMap類型的實(shí)例。TThreadLocalMap是一個(gè)ThreadLocal.ThreadLocalMap類型,這是一個(gè)特殊的Map。
首先看一下在前面的例子中我們是怎么在線程中使用ThreadLocal變量的,
variable.set(Thread.currentThread().getName()); // 設(shè)置ThreadLocal變量 variable.get(); // 讀取ThreadLocal變量
接下來(lái)我們看看ThreadLocal變量的set和get方法。
ThreadLocal.get()
相關(guān)源碼如下:
public T get() { return get(Thread.currentThread()); // (1) } private T get(Thread t) { ThreadLocalMap map = getMap(t); // (2) if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); // (3) if (e != null) { @SuppressWarnings("unchecked") T result = (T) e.value; return result; } } return setInitialValue(t); // (4) } ThreadLocalMap getMap(Thread t) { return t.threadLocals; // (5) }
從代碼(1)可以看到,調(diào)用ThreadLocal的get()方法時(shí),會(huì)將當(dāng)前線程作為參數(shù)傳遞。代碼(2)調(diào)用getMap方法獲取ThreadLocalMap類型變量,如果map不為空則把ThreadLocal實(shí)例作為key獲取值,這個(gè)值就是ThreadLocal變量的值(5)可以看到getMap方法返回的就是Thread類型的threadLocals變量。根據(jù)上述分析我們可以知道:
線程在讀取ThreadLocal變量時(shí),實(shí)際是獲取當(dāng)前線程的threadLocals變量,然后把ThreadLocal實(shí)例當(dāng)做key從threadLocals查詢對(duì)應(yīng)的值。也就是說(shuō)線程讀取的ThreadLocal的實(shí)際值并不是存在ThreadLocal實(shí)例里的,而是存在線程的threadLocals里面,threadLocals是一個(gè)ThreadLocal.ThreadLocalMap,這是一個(gè)特殊的Map,key為ThreadLocal實(shí)例,值為ThreadLocal變量的實(shí)際值。ThreadLoca相當(dāng)于一個(gè)轉(zhuǎn)接口,連接Thread和ThreadLocal。
代碼(4)可以看到如果當(dāng)前線程的threadLocals變量為null,會(huì)調(diào)用ThreadLocal的setInitialValue方法初始化當(dāng)前線程的threadLocals實(shí)例。
private T setInitialValue(Thread t) { T value = initialValue(); ThreadLocalMap map = getMap(t); if (map != null) { map.set(this, value); } else { createMap(t, value); } if (this instanceof TerminatingThreadLocal<?> ttl) { TerminatingThreadLocal.register(ttl); } if (TRACE_VTHREAD_LOCALS) { dumpStackIfVirtualThread(); } return value; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); // (1) }
setInitialValue
方法會(huì)創(chuàng)建參數(shù)傳遞線程的threadLocals值,并且設(shè)置一個(gè)初始化值。從代碼(1)可以看到threadLocals的key為ThreadLocal實(shí)例。
下面再看看ThreadLocal的set方法:
public void set(T value) { set(Thread.currentThread(), value); // (1) if (TRACE_VTHREAD_LOCALS) { dumpStackIfVirtualThread(); } } private void set(Thread t, T value) { ThreadLocalMap map = getMap(t); if (map != null) { map.set(this, value); } else { createMap(t, value); } }
從代碼(1)可以看到,調(diào)用ThreadLocald的set方法會(huì)向當(dāng)前線程的threadLocals變量里設(shè)置傳遞的值value,key為ThreadLocal實(shí)例的引用,和get方法一樣,如果當(dāng)前線程的threadLocals變量為null,則會(huì)創(chuàng)建一個(gè)ThreadLocalMap變量并把value設(shè)置為初始值。
總結(jié):在每個(gè)線程內(nèi)部都有一個(gè)threadLocals變量,該變量類型為ThreadLocal.ThreadLocalMap,其中key為我們定義的ThreadLocal變量的this引用,value則為我們使用set方法設(shè)置的值。每個(gè)線程的本地變量存放在線程自己的內(nèi)存變量threadLocals中。
如果線程不銷毀,那么對(duì)應(yīng)的本地變量就會(huì)一直存在,所以可能存在內(nèi)存溢出,因此使用完畢之后要記得調(diào)用ThreadLocal的remove方法刪除對(duì)應(yīng)線程的threadLocals變量里的值。
注意:ThreadLocal不具備繼承性,也就是說(shuō)子線程并不能訪問(wèn)父線程的ThreadLocal變量。
到此這篇關(guān)于Java中ThreadLocal共享變量的使用的文章就介紹到這了,更多相關(guān)Java ThreadLocal共享變量?jī)?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談常用Java數(shù)據(jù)庫(kù)連接池(小結(jié))
這篇文章主要介紹了淺談常用Java數(shù)據(jù)庫(kù)連接池(小結(jié)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07java 基礎(chǔ)知識(shí)之網(wǎng)絡(luò)通信(TCP通信、UDP通信、多播以及NIO)總結(jié)
這篇文章主要介紹了java 基礎(chǔ)知識(shí)之網(wǎng)絡(luò)通信總結(jié)的相關(guān)資料,包括TCP通信、UDP通信、多播以及NIO,需要的朋友可以參考下2017-03-03一文教你學(xué)會(huì)搭建SpringBoot分布式項(xiàng)目
這篇文章主要為大家詳細(xì)介紹了搭建SpringBoot分布式項(xiàng)目的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01JSON.parseObject和JSON.toJSONString實(shí)例詳解
這篇文章主要為大家詳細(xì)介紹了JSON.parseObject和JSON.toJSONString實(shí)例,具有一定的參考價(jià)值,感興趣的朋友可以參考一下2018-06-06Java實(shí)現(xiàn)經(jīng)典游戲之大魚(yú)吃小魚(yú)
這篇文章主要為大家詳細(xì)介紹了如何利用Java語(yǔ)言實(shí)現(xiàn)經(jīng)典游戲之大魚(yú)吃小魚(yú),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Java游戲開(kāi)發(fā)有一定幫助,需要的可以參考一下2022-08-08詳解領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)之事件驅(qū)動(dòng)與CQRS
這篇文章分析了如何應(yīng)用事件來(lái)分離軟件核心復(fù)雜度。探究CQRS為什么廣泛應(yīng)用于DDD項(xiàng)目中,以及如何落地實(shí)現(xiàn)CQRS框架。當(dāng)然我們也要警惕一些失敗的教訓(xùn),利弊分析以后再去抉擇正確的應(yīng)對(duì)之道2021-06-06Java Class 解析器實(shí)現(xiàn)方法示例
這篇文章主要通過(guò)對(duì)class文件的分析,介紹了Java Class 解析器實(shí)現(xiàn)方法示例,具有一定參考價(jià)值,需要的朋友可以了解下。2017-09-09Spring中獲取Bean方法上的自定義注解問(wèn)題解析
這篇文章主要介紹了Spring中如何獲取Bean方法上的自定義注解,基本的思路就是通過(guò)Spring提供的ApplicationContext#getBeansWithAnnotation+反射來(lái)實(shí)現(xiàn),需要的朋友可以參考下2023-06-06