精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

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

OSC開源社區 ? 來源:冰河技術 ? 2023-08-07 11:39 ? 次閱讀

前言

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

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

對共享變量加鎖雖然能夠保證線程的安全,但是卻增加了開發人員對鎖的使用技能,如果鎖使用不當,則會導致死鎖的問題。而ThreadLocal能夠做到在創建變量后,每個線程對變量訪問時訪問的是線程自己的本地變量

什么是ThreadLocal?

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

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

ThreadLocal使用示例

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

publicclassThreadLocalTest{

privatestaticThreadLocalthreadLocal=newThreadLocal();

publicstaticvoidmain(String[]args){
//創建第一個線程
ThreadthreadA=newThread(()->{
threadLocal.set("ThreadA:"+Thread.currentThread().getName());
System.out.println("線程A本地變量中的值為:"+threadLocal.get());
});
//創建第二個線程
ThreadthreadB=newThread(()->{
threadLocal.set("ThreadB:"+Thread.currentThread().getName());
System.out.println("線程B本地變量中的值為:"+threadLocal.get());
});
//啟動線程A和線程B
threadA.start();
threadB.start();
}
}

運行程序,打印的結果信息如下所示。

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

此時,我們為線程A增加刪除ThreadLocal中的變量的操作,如下所示。

publicclassThreadLocalTest{

privatestaticThreadLocalthreadLocal=newThreadLocal();

publicstaticvoidmain(String[]args){
//創建第一個線程
ThreadthreadA=newThread(()->{
threadLocal.set("ThreadA:"+Thread.currentThread().getName());
System.out.println("線程A本地變量中的值為:"+threadLocal.get());
threadLocal.remove();
System.out.println("線程A刪除本地變量后ThreadLocal中的值為:"+threadLocal.get());
});
//創建第二個線程
ThreadthreadB=newThread(()->{
threadLocal.set("ThreadB:"+Thread.currentThread().getName());
System.out.println("線程B本地變量中的值為:"+threadLocal.get());
System.out.println("線程B沒有刪除本地變量:"+threadLocal.get());
});
//啟動線程A和線程B
threadA.start();
threadB.start();
}
}

此時的運行結果如下所示。

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

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

ThreadLocal原理

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

publicclassThreadimplementsRunnable{
/***********省略N行代碼*************/
ThreadLocal.ThreadLocalMapthreadLocals=null;
ThreadLocal.ThreadLocalMapinheritableThreadLocals=null;
/***********省略N行代碼*************/
}

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

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

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

set()方法

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

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

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

ThreadLocalMapgetMap(Threadt){
returnt.threadLocals;
}

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

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

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

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

也就是創建當前線程的threadLocals變量。

get()方法

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

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

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

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

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

protectedTinitialValue(){
returnnull;
}

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

remove()方法

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

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

remove()方法的實現比較簡單,首先根據當前線程獲取threadLocals成員變量,不為空,則直接移除value的值。

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

ThreadLocal變量不具有傳遞性

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

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

publicclassThreadLocalTest{

privatestaticThreadLocalthreadLocal=newThreadLocal();

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

運行這段代碼輸出的結果信息如下所示。

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

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

InheritableThreadLocal使用示例

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

publicclassThreadLocalTest{

privatestaticThreadLocalthreadLocal=newInheritableThreadLocal();

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

此時,運行程序輸出的結果信息如下所示。

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

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

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()方法。也就是說,當調用ThreadLocal的set()方法時,創建的是當前Thread線程的inheritableThreadLocals成員變量而不再是threadLocals成員變量。

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

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類的構造方法最終調用的是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()方法中會判斷傳遞的inheritThreadLocals變量是否為true,同時父線程中的inheritableThreadLocals是否為null,如果傳遞的inheritThreadLocals變量為true,同時,父線程中的inheritableThreadLocals不為null,則調用ThreadLocal類的createInheritedMap()方法。

staticThreadLocalMapcreateInheritedMap(ThreadLocalMapparentMap){
returnnewThreadLocalMap(parentMap);
}

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

接下來,我們來看看ThreadLocalMap的構造函數都干了啥,如下所示。

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

for(intj=0;jkey=(ThreadLocal)e.get();
if(key!=null){
//調用重寫的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的構造函數中,調用了InheritableThreadLocal類重寫的childValue()方法。而InheritableThreadLocal類通過重寫getMap()方法和createMap()方法,讓本地變量保存到了Thread線程的inheritableThreadLocals變量中,線程通過InheritableThreadLocal類的set()方法和get()方法設置變量時,就會創建當前線程的inheritableThreadLocals變量。

此時,如果父線程創建子線程,在Thread類的構造函數中會把父線程中的inheritableThreadLocals變量里面的本地變量復制一份保存到子線程的inheritableThreadLocals變量中。





審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 存儲器
    +關注

    關注

    38

    文章

    7455

    瀏覽量

    163623
  • Thread編程
    +關注

    關注

    0

    文章

    3

    瀏覽量

    877
  • 內存溢出
    +關注

    關注

    0

    文章

    10

    瀏覽量

    1193

原文標題:一文讓你徹底掌握ThreadLocal

文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    徹底掌握MOS管

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

    ThreadLocal實例應用

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

    ThreadLocal的定義、用法及優點

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

    徹底理解DFT

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

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

    份白皮書,徹底了解比特幣
    發表于 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張圖徹底理解晶體管開關電路圖

    8張徹底理解晶體管開關電路圖
    的頭像 發表于 02-05 12:41 ?1.1w次閱讀

    ThreadLocal發生內存泄漏的原因

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

    如何使用ThreadLocal來避免內存泄漏

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

    FastThreadLocal快在哪里

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

    ThreadLocal的作用以及應用場景

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

    ThreadLocal源碼解析及實戰應用

    ThreadLocal個關于創建線程局部變量的類。
    的頭像 發表于 01-29 14:53 ?452次閱讀

    ThreadLocal是什么

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

    ThreadLocal父子線程之間該如何傳遞數據?

    比如會有以下的這種代碼的實現。在子線程中調用 get 時,我們拿到的 Thread 對象是當前子線程對象,對吧,每個線程都有自己獨立的 ThreadLocal,那么當前子線程
    的頭像 發表于 02-20 11:26 ?878次閱讀

    掌握Linux常用命令

    掌握Linux40個命令
    的頭像 發表于 04-03 11:38 ?632次閱讀

    ThreadLocal基本內容與用法

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