最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java ThreadLocal類使用詳解

 更新時(shí)間:2022年07月05日 11:23:43   作者:? Pymjl?  ?  
這篇文章主要介紹了Java ThreadLocal類詳解,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下

前言

這幾天看《Java并發(fā)編程之美》的時(shí)候又遇到了ThradLocal這個(gè)類,不得不說(shuō),這個(gè)類在平時(shí)很多場(chǎng)景都遇得到,所以對(duì)其進(jìn)行一個(gè)系統(tǒng)性的學(xué)習(xí),然后再輸出成這篇博客。 那么,什么是ThreadLocal呢? 我們都知道,多線程訪問(wèn)同一個(gè)共享變量很容易出現(xiàn)并發(fā)問(wèn)題,特別是當(dāng)多個(gè)線程對(duì)同一個(gè)共享變量進(jìn)行寫入操作時(shí)。一般為了避免這種情況,我們會(huì)使用synchronized這個(gè)關(guān)鍵字對(duì)代碼塊加鎖。但是這種方式一是會(huì)讓沒(méi)獲取到鎖的線程進(jìn)行阻塞等待,二是需要使用者對(duì)鎖有一定的了解,無(wú)疑提高了編程的難度。其實(shí)ThreadLocal 就可以做這件事情,雖然ThreadLocal 并不是為了解決這個(gè)問(wèn)題而出現(xiàn)的。 ThreadLocal 是JDK 包提供的,它提供了線程本地變量,也就是如果你創(chuàng)建了一個(gè)ThreadLocal 變量,那么訪問(wèn)這個(gè)變量的每個(gè)線程都會(huì)有這個(gè)變量的一個(gè)本地副本。當(dāng)多個(gè)線程操作這個(gè)變量時(shí),實(shí)際操作的是自己本地內(nèi)存里面的變量,從而避免了線程安全問(wèn)題。

如圖所示: 

快速開始

接下來(lái)我們就先用一個(gè)簡(jiǎn)單的樣例給大家展示一下ThreadLocal的基本用法

package cuit.pymjl.thradlocal;

/**
 * @author Pymjl
 * @version 1.0
 * @date 2022/7/1 10:56
 **/
public class MainTest {
 ? ?static ThreadLocal<String> threadLocal = new ThreadLocal<>();
?
 ? ?static void print(String str) {
 ? ? ? ?//打印當(dāng)前線程中本地內(nèi)存中本地變量的值
 ? ? ? ?System.out.println(str + " :" + threadLocal.get());
 ? ? ? ?//清除本地內(nèi)存中的本地變量
 ? ? ? ?threadLocal.remove();
 ?  }
?
 ? ?public static void main(String[] args) {
 ? ? ? ?Thread t1 = new Thread(new Runnable() {
 ? ? ? ? ? ?@Override
 ? ? ? ? ? ?public void run() {
 ? ? ? ? ? ? ? ?//設(shè)置線程1中本地變量的值
 ? ? ? ? ? ? ? ?threadLocal.set("thread1 local variable");
 ? ? ? ? ? ? ? ?//調(diào)用打印方法
 ? ? ? ? ? ? ? ?print("thread1");
 ? ? ? ? ? ? ? ?//打印本地變量
 ? ? ? ? ? ? ? ?System.out.println("after remove : " + threadLocal.get());
 ? ? ? ? ?  }
 ? ? ?  });
?
 ? ? ? ?Thread t2 = new Thread(new Runnable() {
 ? ? ? ? ? ?@Override
 ? ? ? ? ? ?public void run() {
 ? ? ? ? ? ? ? ?//設(shè)置線程1中本地變量的值
 ? ? ? ? ? ? ? ?threadLocal.set("thread2 local variable");
 ? ? ? ? ? ? ? ?//調(diào)用打印方法
 ? ? ? ? ? ? ? ?print("thread2");
 ? ? ? ? ? ? ? ?//打印本地變量
 ? ? ? ? ? ? ? ?System.out.println("after remove : " + threadLocal.get());
 ? ? ? ? ?  }
 ? ? ?  });
?
 ? ? ? ?t1.start();
 ? ? ? ?t2.start();
 ?  }
}

運(yùn)行結(jié)果如圖所示:

ThreadLocal的原理

ThreadLocal相關(guān)類圖

我們先來(lái)看一下ThreadLocal 相關(guān)類的類圖結(jié)構(gòu),如圖所示: 

 由該圖可知, Thread 類中有一個(gè)threadLocals 和一個(gè)inheritableThreadLocals , 它們都是ThreadLocalMap 類型的變量, 而ThreadLocalMap 是一個(gè)定制化的Hashmap 。在默認(rèn)情況下, 每個(gè)線程中的這兩個(gè)變量都為null ,只有當(dāng)前線程第一次調(diào)用ThreadLocal 的set 或者get 方法時(shí)才會(huì)創(chuàng)建它們。其實(shí)每個(gè)線程的本地變量不是存放在ThreadLocal 實(shí)例里面,而是存放在調(diào)用線程的threadLocals 變量里面。也就是說(shuō), ThreadLocal 類型的本地變量存放在具體的線程內(nèi)存空間中。ThreadLocal 就是一個(gè)工具殼,它通過(guò)set 方法把value 值放入調(diào)用線程的threadLocals 里面并存放起來(lái), 當(dāng)調(diào)用線程調(diào)用它的get 方法時(shí),再?gòu)漠?dāng)前線程的threadLocals 變量里面將其拿出來(lái)使用。 如果調(diào)用線程一直不終止, 那么這個(gè)本地變量會(huì)一直存放在調(diào)用線程的threadLocals 變量里面,所以當(dāng)不需要使用本地變量時(shí)可以通過(guò)調(diào)用ThreadLocal 變量的remove 方法,從當(dāng)前線程的threadLocals 里面刪除該本地變量。另外, Thread 里面的threadLocals 為何被設(shè)計(jì)為map 結(jié)構(gòu)?很明顯是因?yàn)槊總€(gè)線程可以關(guān)聯(lián)多個(gè)ThreadLocal 變量。 接下來(lái)我們來(lái)看看ThreadLocal的set、get、以及remove的源碼

set

 ? ?public void set(T value) {
 ? ? ? ?// 1.獲取當(dāng)前線程(調(diào)用者線程)
 ? ? ? ?Thread t = Thread.currentThread();
 ? ? ? ?// 2.以當(dāng)前線程作為key值,去查找對(duì)應(yīng)的線程變量,找到對(duì)應(yīng)的map
 ? ? ? ?ThreadLocalMap map = getMap(t);
 ? ? ? ?if (map != null) {
 ? ? ? ? ? ?// 3.如果map不為null,則直接添加元素
 ? ? ? ? ? ?map.set(this, value);
 ? ? ?  } else {
 ? ? ? ? ? ?// 4.否則就先創(chuàng)建map,再添加元素
 ? ? ? ? ? ?createMap(t, value);
 ? ? ?  }
 ?  }
 ? ?void createMap(Thread t, T firstValue) {
 ? ? ? ?/**
 ? ? ? ? * 這里是創(chuàng)建一個(gè)ThreadLocalMap,以當(dāng)前調(diào)用線程的實(shí)例對(duì)象為key,初始值為value
 ? ? ? ? * 然后放入當(dāng)前線程的Therad.threadLocals屬性里面
 ? ? ? ? */
 ? ? ? ?t.threadLocals = new ThreadLocalMap(this, firstValue);
 ?  }
 ? ?ThreadLocalMap getMap(Thread t) {
 ? ? ? ?//這里就是直接獲取調(diào)用線程的成員屬性threadlocals
 ? ? ? ?return t.threadLocals;
 ?  }

get

 ? ?public T get() {
 ? ? ? ?// 1.獲取當(dāng)前線程
 ? ? ? ?Thread t = Thread.currentThread();
 ? ? ? ?// 2.獲取當(dāng)前線程的threadlocals,即ThreadLocalMap
 ? ? ? ?ThreadLocalMap map = getMap(t);
 ? ? ? ?// 3.如果map不為null,則直接返回對(duì)應(yīng)的值
 ? ? ? ?if (map != null) {
 ? ? ? ? ? ?ThreadLocalMap.Entry e = map.getEntry(this);
 ? ? ? ? ? ?if (e != null) {
 ? ? ? ? ? ? ? ?@SuppressWarnings("unchecked")
 ? ? ? ? ? ? ? ?T result = (T)e.value;
 ? ? ? ? ? ? ? ?return result;
 ? ? ? ? ?  }
 ? ? ?  }
 ? ? ? ?// 4.否則,則進(jìn)行初始化
 ? ? ? ?return setInitialValue();
 ?  }

下面是setInitialValue的代碼

private T setInitialValue() {
 ? ?//初始化屬性,其實(shí)就是null
 ? ?T value = initialValue();
 ? ?//獲取當(dāng)前線程
 ? ?Thread t = Thread.currentThread();
 ? ?//通過(guò)當(dāng)前線程獲取ThreadLocalMap
 ? ?ThreadLocalMap map = getMap(t);
 ? ?//如果map不為null,則直接添加元素
 ? ?if (map != null) {
 ? ? ? ?map.set(this, value);
 ?  } else {
 ? ? ? ?//否則就創(chuàng)建,然后將創(chuàng)建好的map放入當(dāng)前線程的屬性threadlocals
 ? ? ? ?createMap(t, value);
 ?  }
 ? ? ? ?//將當(dāng)前ThreadLocal實(shí)例注冊(cè)進(jìn)TerminatingThreadLocal類里面
 ? ?if (this instanceof TerminatingThreadLocal) {
 ? ? ? ?TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
 ?  }
 ? ?return value;
}

這里我需要補(bǔ)充說(shuō)明一下TerminatingThreadLocal。這個(gè)類是jdk11新出的,jdk8中并沒(méi)有這個(gè)類,所以在網(wǎng)上很多源碼分析中并未看見這個(gè)類的相關(guān)說(shuō)明。 這個(gè)類我看了一下源碼,其作用應(yīng)該是避免ThreadLocal內(nèi)存泄露的問(wèn)題(感興趣的可以去看看源碼,若有錯(cuò)誤,還請(qǐng)指正)。 這是官方對(duì)其的解釋:

/**
 * A thread-local variable that is notified when a thread terminates and
 * it has been initialized in the terminating thread (even if it was
 * initialized with a null value).
 * 一個(gè)線程局部變量,
 * 當(dāng)一個(gè)線程終止并且它已經(jīng)在終止線程中被初始化時(shí)被通知(即使它被初始化為一個(gè)空值)。
 */

remove

 ? ? public void remove() {
 ? ? ? ? //如果當(dāng)前線程的threadLocals 變量不為空, 則刪除當(dāng)前線程中指定ThreadLocal 實(shí)例的本地變量。
 ? ? ? ? ThreadLocalMap m = getMap(Thread.currentThread());
 ? ? ? ? if (m != null) {
 ? ? ? ? ? ? m.remove(this);
 ? ? ? ? }
 ? ? }

小結(jié)

在每個(gè)線程內(nèi)部都有一個(gè)名為threadLocals 的成員變量, 該變量的類型為Hash Map , 其中key 為我們定義的ThreadLocal 變量的this 引用, value 則為我們使用set 方法設(shè)置的值。每個(gè)線程的本地變量存放在線程自己的內(nèi)存變量threadLocals 中,如果當(dāng)前線程一直不消亡, 那么這些本地變量會(huì)一直存在, 所以可能會(huì)造成內(nèi)存溢出, 因此使用完畢后要記得調(diào)用ThreadLocal 的remove 方法刪除對(duì)應(yīng)線程的threadLocals 中的本地變量。

ThreadLocal內(nèi)存泄露

為什么會(huì)出現(xiàn)內(nèi)存泄漏?

ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個(gè)ThreadLocal沒(méi)有外部強(qiáng)引用來(lái)引用它,那么系統(tǒng) GC 的時(shí)候,這個(gè)ThreadLocal勢(shì)必會(huì)被回收,這樣一來(lái),ThreadLocalMap中就會(huì)出現(xiàn)key為null的Entry,就沒(méi)有辦法訪問(wèn)這些key為null的Entry的value,如果當(dāng)前線程再遲遲不結(jié)束的話,這些key為null的Entry的value就會(huì)一直存在一條強(qiáng)引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠(yuǎn)無(wú)法回收,造成內(nèi)存泄漏。 其實(shí),ThreadLocalMap的設(shè)計(jì)中已經(jīng)考慮到這種情況,也加上了一些防護(hù)措施:在ThreadLocal的get(),set(),remove()的時(shí)候都會(huì)清除線程ThreadLocalMap里所有key為null的value。 但是這些被動(dòng)的預(yù)防措施并不能保證不會(huì)內(nèi)存泄漏:

  • 使用static的ThreadLocal,延長(zhǎng)了ThreadLocal的生命周期,可能導(dǎo)致的內(nèi)存泄漏
  • 分配使用了ThreadLocal又不再調(diào)用get(),set(),remove()方法,那么就會(huì)導(dǎo)致內(nèi)存泄漏

為什么使用弱引用?

既然我們都知道,使用了弱引用會(huì)造成ThreadLocalMap內(nèi)存泄漏,那么官方為什么依然使用弱引用而不是強(qiáng)引用呢?這就要從使用弱引用和強(qiáng)引用的區(qū)別來(lái)說(shuō)起了:

  • 如果使用強(qiáng)引用:我們知道,ThreadLocalMap的生命周期基本和Thread的生命周期一樣,當(dāng)前線程如果沒(méi)有終止,那么ThreadLocalMap始終不會(huì)被GC回收,而ThreadLocalMap持有對(duì)ThreadLocal的強(qiáng)引用,那么ThreadLocal也不會(huì)被回收,當(dāng)線程生命周期長(zhǎng),如果沒(méi)有手動(dòng)刪除,則會(huì)造成kv累積,從而導(dǎo)致OOM
  • 如果使用弱引用:弱引用中的對(duì)象具有很短的聲明周期,因?yàn)樵谙到y(tǒng)GC時(shí),只要發(fā)現(xiàn)弱引用,不管堆空間是否足夠,都會(huì)將對(duì)象進(jìn)行回收。而當(dāng)ThreadLocal的強(qiáng)引用被回收時(shí),ThreadLocalMap所持有的弱引用也會(huì)被回收,如果沒(méi)有手動(dòng)刪除kv,那么會(huì)造成value累積,也會(huì)導(dǎo)致OOM

對(duì)比可知,使用弱引用至少可以保證不會(huì)因?yàn)閙ap的key累積從而導(dǎo)致OOM,而對(duì)應(yīng)的value可以通過(guò)remove,get,set方法在下一次調(diào)用時(shí)被清除??梢?,內(nèi)存泄露的根源不是弱引用,而是ThreadLocalMap的生命周期和Thread一樣長(zhǎng),造成累積導(dǎo)致的

解決方法

既然問(wèn)題的根源是value的累積造成OOM,那么我們對(duì)癥下藥,每次使用完ThreadLocal調(diào)用remove()方法清理掉就行了。

總結(jié)

到此這篇關(guān)于Java ThreadLocal類詳解的文章就介紹到這了,更多相關(guān)Java ThreadLocal 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解JVM的分代模型

    詳解JVM的分代模型

    這篇文章主要介紹了JVM的分代模型的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)Java虛擬機(jī)相關(guān)知識(shí),感興趣的朋友可以了解下
    2020-10-10
  • 關(guān)于Java并發(fā)編程中線程間協(xié)作的兩種方式

    關(guān)于Java并發(fā)編程中線程間協(xié)作的兩種方式

    這篇文章主要介紹了關(guān)于Java并發(fā)編程中線程間協(xié)作的兩種方式,當(dāng)隊(duì)列滿時(shí),生產(chǎn)者需要等待隊(duì)列有空間才能繼續(xù)往里面放入商品,而在等待的期間內(nèi),生產(chǎn)者必須釋放對(duì)臨界資源的占用權(quán),這是消費(fèi)者模式,需要的朋友可以參考下
    2023-07-07
  • Java實(shí)現(xiàn)設(shè)計(jì)模式之責(zé)任鏈模式

    Java實(shí)現(xiàn)設(shè)計(jì)模式之責(zé)任鏈模式

    責(zé)任鏈模式是一種行為設(shè)計(jì)模式,允許你將請(qǐng)求沿著處理鏈發(fā)送,然后處理者都可對(duì)其進(jìn)行處理,完成后可以再將其傳遞給下一個(gè)處理者。下面將會(huì)舉例說(shuō)明什么是責(zé)任鏈模式,責(zé)任鏈模式該如何使用
    2022-08-08
  • java基礎(chǔ)之Object類

    java基礎(chǔ)之Object類

    這篇文章主要介紹了java基礎(chǔ)之Object類 的相關(guān)資料,需要的朋友可以參考下
    2015-06-06
  • 詳解Spring MVC事務(wù)配置

    詳解Spring MVC事務(wù)配置

    這篇文章主要介紹了詳解Spring MVC事務(wù)配置,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-04-04
  • Springboot 使用 JSR 303 對(duì) Controller 控制層校驗(yàn)及 Service 服務(wù)層 AOP 校驗(yàn) 使用消息資源文件對(duì)消息國(guó)際化

    Springboot 使用 JSR 303 對(duì) Controller 控制層校驗(yàn)及 Service 服務(wù)層 AOP 校驗(yàn)

    這篇文章主要介紹了Springboot 使用 JSR 303 對(duì) Controller 控制層校驗(yàn)及 Service 服務(wù)層 AOP 校驗(yàn) 使用消息資源文件對(duì)消息國(guó)際化的相關(guān)知識(shí),需要的朋友可以參考下
    2017-12-12
  • SpringBoot?Validation提示信息國(guó)際化配置方式

    SpringBoot?Validation提示信息國(guó)際化配置方式

    這篇文章主要介紹了SpringBoot?Validation提示信息國(guó)際化配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • SpringBoot集成AOP的代碼示例

    SpringBoot集成AOP的代碼示例

    AOP是一種編程范式,它旨在將橫切關(guān)注點(diǎn)(cross-cutting concerns)從應(yīng)用程序的業(yè)務(wù)邏輯中分離出來(lái),橫切關(guān)注點(diǎn)是那些在多個(gè)模塊中重復(fù)出現(xiàn)的功能,如日志記錄、性能監(jiān)控、事務(wù)管理、安全控制等,本文介紹了Spring Boot如何集成AOP,需要的朋友可以參考下
    2024-09-09
  • MyBatis在Mapper中傳遞多個(gè)參數(shù)的四種方法詳解

    MyBatis在Mapper中傳遞多個(gè)參數(shù)的四種方法詳解

    這篇文章主要介紹了MyBatis在Mapper中傳遞多個(gè)參數(shù)的四種方法,文章通過(guò)代碼示例介紹的非常詳細(xì),具有一定的參考價(jià)值,,需要的朋友可以參考下
    2023-09-09
  • SpringBoot JS-SDK自定義微信分享的實(shí)現(xiàn)

    SpringBoot JS-SDK自定義微信分享的實(shí)現(xiàn)

    這篇文章主要介紹了SpringBoot JS-SDK自定義微信分享的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09

最新評(píng)論

东辽县| 鄂伦春自治旗| 麻阳| 龙里县| 宜昌市| 扎兰屯市| 个旧市| 彰化县| 瑞丽市| 平阴县| 张家界市| 卓资县| 宁蒗| 长岛县| 璧山县| 登封市| 金川县| 平利县| 蒙自县| 崇阳县| 临朐县| 依安县| 涿州市| 大关县| 耒阳市| 商河县| 伽师县| 谢通门县| 卓资县| 博乐市| 桃江县| 九龙城区| 富蕴县| 黎城县| 上饶县| 南平市| 堆龙德庆县| 来凤县| 西乡县| 江西省| 台湾省|