0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

什么是ThreadLocal?一文讓你徹底掌握ThreadLocal

OSC開源社區(qū) ? 來源:冰河技術(shù) ? 2023-08-07 11:39 ? 次閱讀

前言

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

b2620dde-32af-11ee-9e74-dac502259ad0.jpg

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

什么是ThreadLocal?

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

b275f966-32af-11ee-9e74-dac502259ad0.jpg

ThreadLocal使用示例

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

publicclassThreadLocalTest{

privatestaticThreadLocalthreadLocal=newThreadLocal();

publicstaticvoidmain(String[]args){
//創(chuàng)建第一個(gè)線程
ThreadthreadA=newThread(()->{
threadLocal.set("ThreadA:"+Thread.currentThread().getName());
System.out.println("線程A本地變量中的值為:"+threadLocal.get());
});
//創(chuàng)建第二個(gè)線程
ThreadthreadB=newThread(()->{
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中的變量的操作,如下所示。

publicclassThreadLocalTest{

privatestaticThreadLocalthreadLocal=newThreadLocal();

publicstaticvoidmain(String[]args){
//創(chuàng)建第一個(gè)線程
ThreadthreadA=newThread(()->{
threadLocal.set("ThreadA:"+Thread.currentThread().getName());
System.out.println("線程A本地變量中的值為:"+threadLocal.get());
threadLocal.remove();
System.out.println("線程A刪除本地變量后ThreadLocal中的值為:"+threadLocal.get());
});
//創(chuàng)建第二個(gè)線程
ThreadthreadB=newThread(()->{
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訪問。

ThreadLocal原理

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

publicclassThreadimplementsRunnable{
/***********省略N行代碼*************/
ThreadLocal.ThreadLocalMapthreadLocals=null;
ThreadLocal.ThreadLocalMapinheritableThreadLocals=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()方法的源代碼如下所示。

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

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

ThreadLocalMapgetMap(Threadt){
returnt.threadLocals;
}

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

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

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

voidcreateMap(Threadt,TfirstValue){
t.threadLocals=newThreadLocalMap(this,firstValue);
}

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

get()方法

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

publicTget(){
//獲取當(dāng)前線程
Threadt=Thread.currentThread();
//獲取當(dāng)前線程的threadLocals成員變量
ThreadLocalMapmap=getMap(t);
//獲取的threadLocals變量不為空
if(map!=null){
//返回本地變量對應(yīng)的值
ThreadLocalMap.Entrye=map.getEntry(this);
if(e!=null){
@SuppressWarnings("unchecked")
Tresult=(T)e.value;
returnresult;
}
}
//初始化threadLocals成員變量的值
returnsetInitialValue();
}

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

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

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

protectedTinitialValue(){
returnnull;
}

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

remove()方法

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

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

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

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

ThreadLocal變量不具有傳遞性

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

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

publicclassThreadLocalTest{

privatestaticThreadLocalthreadLocal=newThreadLocal();

publicstaticvoidmain(String[]args){
//在主線程中設(shè)置值
threadLocal.set("ThreadLocalTest");
//在子線程中獲取值
Threadthread=newThread(newRunnable(){
@Override
publicvoidrun(){
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í)例,如下所示。

publicclassThreadLocalTest{

privatestaticThreadLocalthreadLocal=newInheritableThreadLocal();

publicstaticvoidmain(String[]args){
//在主線程中設(shè)置值
threadLocal.set("ThreadLocalTest");
//在子線程中獲取值
Threadthread=newThread(newRunnable(){
@Override
publicvoidrun(){
System.out.println("子線程獲取值:"+threadLocal.get());
}
});
//啟動(dòng)子線程
thread.start();
//在主線程中獲取值
System.out.println("主線程獲取值:"+threadLocal.get());
}
}

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

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

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

b2ce9eae-32af-11ee-9e74-dac502259ad0.jpg

InheritableThreadLocal原理

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

publicclassInheritableThreadLocalextendsThreadLocal{
protectedTchildValue(TparentValue){
returnparentValue;
}

ThreadLocalMapgetMap(Threadt){
returnt.inheritableThreadLocals;
}

voidcreateMap(Threadt,TfirstValue){
t.inheritableThreadLocals=newThreadLocalMap(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)造方法了,如下所示。

publicThread(){
init(null,null,"Thread-"+nextThreadNum(),0);
}

publicThread(Runnabletarget){
init(null,target,"Thread-"+nextThreadNum(),0);
}

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

publicThread(ThreadGroupgroup,Runnabletarget){
init(group,target,"Thread-"+nextThreadNum(),0);
}

publicThread(Stringname){
init(null,null,name,0);
}

publicThread(ThreadGroupgroup,Stringname){
init(group,null,name,0);
}

publicThread(Runnabletarget,Stringname){
init(null,target,name,0);
}

publicThread(ThreadGroupgroup,Runnabletarget,Stringname){
init(group,target,name,0);
}

publicThread(ThreadGroupgroup,Runnabletarget,Stringname,
longstackSize){
init(group,target,name,stackSize);
}

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

privatevoidinit(ThreadGroupg,Runnabletarget,Stringname,
longstackSize,AccessControlContextacc,
booleaninheritThreadLocals){
/************省略部分源碼************/
if(inheritThreadLocals&&parent.inheritableThreadLocals!=null)
this.inheritableThreadLocals=
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/*StashthespecifiedstacksizeincasetheVMcares*/
this.stackSize=stackSize;

/*SetthreadID*/
tid=nextThreadID();
}

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

staticThreadLocalMapcreateInheritedMap(ThreadLocalMapparentMap){
returnnewThreadLocalMap(parentMap);
}

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

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

privateThreadLocalMap(ThreadLocalMapparentMap){
Entry[]parentTable=parentMap.table;
intlen=parentTable.length;
setThreshold(len);
table=newEntry[len];

for(intj=0;jkey=(ThreadLocal)e.get();
if(key!=null){
//調(diào)用重寫的childValue方法
Objectvalue=key.childValue(e.value);
Entryc=newEntry(key,value);
inth=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變量中。





審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 存儲(chǔ)器
    +關(guān)注

    關(guān)注

    38

    文章

    7366

    瀏覽量

    163096
  • Thread編程
    +關(guān)注

    關(guān)注

    0

    文章

    3

    瀏覽量

    873
  • 內(nèi)存溢出
    +關(guān)注

    關(guān)注

    0

    文章

    10

    瀏覽量

    1179

原文標(biāo)題:一文讓你徹底掌握ThreadLocal

文章出處:【微信號(hào):OSC開源社區(qū),微信公眾號(hào):OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    徹底掌握MOS管

    基礎(chǔ)知識(shí)中 MOS 部分遲遲未整理,實(shí)際分享的電路中大部分常用電路都用到了MOS管, 今天勢必要來篇文章,徹底掌握mos管!
    發(fā)表于 07-05 11:56 ?2.9w次閱讀

    ThreadLocal實(shí)例應(yīng)用

    ThreadLocal相信大家都用過,但知道他的原理嗎,今天了不起帶大家學(xué)習(xí)ThreadLocalThreadLocal是什么 在多線程編程中,經(jīng)常會(huì)遇到需要在不同線程中共享數(shù)據(jù)
    的頭像 發(fā)表于 09-30 10:19 ?587次閱讀
    <b class='flag-5'>ThreadLocal</b>實(shí)例應(yīng)用

    ThreadLocal的定義、用法及優(yōu)點(diǎn)

    ThreadLocal 簡介 ThreadLocal是Java中個(gè)非常重要的線程技術(shù)。它可以每個(gè)線程都擁有自己的變量副本,避免了線程間的競爭和數(shù)據(jù)泄露問題。在本文中,我們將詳細(xì)介紹
    的頭像 發(fā)表于 09-30 10:14 ?879次閱讀
    <b class='flag-5'>ThreadLocal</b>的定義、用法及優(yōu)點(diǎn)

    徹底理解DFT

    從本期開始,E課網(wǎng)將為大家推出系列的‘徹底理解’專題。該專題的目的是大家更好的理解并弄清楚設(shè)計(jì)過程中所面臨的各種問題,在對問題充分理
    發(fā)表于 05-25 15:32

    份白皮書,徹底了解比特幣

    份白皮書,徹底了解比特幣
    發(fā)表于 03-05 15:02 ?26次下載
    <b class='flag-5'>一</b>份白皮書,<b class='flag-5'>讓</b><b class='flag-5'>你</b><b class='flag-5'>徹底</b>了解比特幣

    8張圖徹底理解晶體管開關(guān)電路圖

    8張徹底理解晶體管開關(guān)電路圖
    的頭像 發(fā)表于 02-05 12:41 ?1w次閱讀

    ThreadLocal發(fā)生內(nèi)存泄漏的原因

    前言 ThreadLocal 的作用是提供線程內(nèi)的局部變量,這種變量在線程的生命周期內(nèi)起作用,減少同個(gè)線程內(nèi)多個(gè)函數(shù)或者組件之間些公共變量的傳遞的復(fù)雜度。但是如果濫用 ThreadLoca
    的頭像 發(fā)表于 05-05 16:23 ?3605次閱讀

    如何使用ThreadLocal來避免內(nèi)存泄漏

    本次給大家介紹重要的工具ThreadLocal。講解內(nèi)容如下,同時(shí)介紹什么場景下發(fā)生內(nèi)存泄漏,如何復(fù)現(xiàn)內(nèi)存泄漏,如何正確使用它來避免內(nèi)存泄漏。 ThreadLocal是什么?有哪些用途
    的頭像 發(fā)表于 08-20 09:29 ?4109次閱讀
    如何使用<b class='flag-5'>ThreadLocal</b>來避免內(nèi)存泄漏

    FastThreadLocal快在哪里

    netty還要自己造個(gè)FastThreadLocal?FastThreadLocal快在哪里? 這需要從jdk ThreadLocal的本身說起。如下圖: 在java線程中,每個(gè)線程都有個(gè)
    的頭像 發(fā)表于 09-13 09:17 ?1295次閱讀

    ThreadLocal的作用以及應(yīng)用場景

    個(gè)簡單的例子:目前有100個(gè)學(xué)生等待簽字,但是老師只有個(gè)筆,那老師只能按順序的分給每個(gè)學(xué)生,等待A學(xué)生簽字完成然后將筆交給B學(xué)生,這就類似Lock,Synchronized的方式。而ThreadLocal是,老師直接拿出一
    的頭像 發(fā)表于 09-19 10:56 ?1264次閱讀

    ThreadLocal源碼解析及實(shí)戰(zhàn)應(yīng)用

    ThreadLocal個(gè)關(guān)于創(chuàng)建線程局部變量的類。
    的頭像 發(fā)表于 01-29 14:53 ?404次閱讀

    ThreadLocal是什么

    和HashMap的最大的不同在于,ThreadLocalMap結(jié)構(gòu)非常簡單,沒有next引用,也就是說ThreadLocalMap中解決Hash沖突的方式并非鏈表的方式,而是采用線性探測的方式。
    的頭像 發(fā)表于 01-30 11:36 ?1376次閱讀

    ThreadLocal父子線程之間該如何傳遞數(shù)據(jù)?

    比如會(huì)有以下的這種代碼的實(shí)現(xiàn)。在子線程中調(diào)用 get 時(shí),我們拿到的 Thread 對象是當(dāng)前子線程對象,對吧,每個(gè)線程都有自己獨(dú)立的 ThreadLocal,那么當(dāng)前子線程
    的頭像 發(fā)表于 02-20 11:26 ?803次閱讀

    掌握Linux常用命令

    掌握Linux40個(gè)命令
    的頭像 發(fā)表于 04-03 11:38 ?575次閱讀

    ThreadLocal基本內(nèi)容與用法

    下面我們就來看看道哥都用的ThreadLocal。 1 ThreadLocal來自哪里 Since : 1.2 Author : Josh Bloch and Doug Lea 又是并發(fā)大佬們
    的頭像 發(fā)表于 10-13 11:39 ?388次閱讀