亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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)上很多源碼分析中并未看見(jiàn)這個(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í)被清除??梢?jiàn),內(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)文章

  • MyBatis框架中mybatis配置文件詳細(xì)介紹

    MyBatis框架中mybatis配置文件詳細(xì)介紹

    這篇文章主要介紹了MyBatis框架中mybatis配置文件詳細(xì)介紹,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • freemarker?jsp?java內(nèi)存方式實(shí)現(xiàn)分頁(yè)示例

    freemarker?jsp?java內(nèi)存方式實(shí)現(xiàn)分頁(yè)示例

    這篇文章主要介紹了freemarker?jsp?java內(nèi)存方式實(shí)現(xiàn)分頁(yè)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • ActiveMQ消息隊(duì)列技術(shù)融合Spring過(guò)程解析

    ActiveMQ消息隊(duì)列技術(shù)融合Spring過(guò)程解析

    這篇文章主要介紹了ActiveMQ消息隊(duì)列技術(shù)融合Spring過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-11-11
  • Java字符串中指定部分反轉(zhuǎn)的三種方式

    Java字符串中指定部分反轉(zhuǎn)的三種方式

    一些面試官可能在面試Java基礎(chǔ)的時(shí)候,讓你說(shuō)一下字符串反轉(zhuǎn),會(huì)手撕代碼,所以下面這篇文章主要給大家介紹了關(guān)于Java字符串中指定部分反轉(zhuǎn)的三種方式,需要的朋友可以參考下
    2022-01-01
  • java開源項(xiàng)目jeecgboot的超詳細(xì)解析

    java開源項(xiàng)目jeecgboot的超詳細(xì)解析

    JeecgBoot是一款基于BPM的低代碼平臺(tái),下面這篇文章主要給大家介紹了關(guān)于java開源項(xiàng)目jeecgboot的相關(guān)資料,文中通過(guò)圖文以及實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-10-10
  • 詳解基于SpringBoot使用AOP技術(shù)實(shí)現(xiàn)操作日志管理

    詳解基于SpringBoot使用AOP技術(shù)實(shí)現(xiàn)操作日志管理

    這篇文章主要介紹了詳解基于SpringBoot使用AOP技術(shù)實(shí)現(xiàn)操作日志管理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • MyEclipse+Tomcat+MAVEN+SVN項(xiàng)目完整環(huán)境搭建(圖文教程)

    MyEclipse+Tomcat+MAVEN+SVN項(xiàng)目完整環(huán)境搭建(圖文教程)

    這篇文章主要介紹了MyEclipse+Tomcat+MAVEN+SVN項(xiàng)目完整環(huán)境搭建(圖文教程),非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2017-12-12
  • C/C++中的struct結(jié)構(gòu)體詳細(xì)解讀

    C/C++中的struct結(jié)構(gòu)體詳細(xì)解讀

    這篇文章主要介紹了C/C++中的struct結(jié)構(gòu)體詳細(xì)解讀,結(jié)構(gòu)體是由一批數(shù)據(jù)組合而成的結(jié)構(gòu)型數(shù)據(jù),組成結(jié)構(gòu)型數(shù)據(jù)的每個(gè)數(shù)據(jù)稱為結(jié)構(gòu)型數(shù)據(jù)的“成員”,其描述了一塊內(nèi)存區(qū)間的大小及意義,需要的朋友可以參考下
    2023-10-10
  • Java中Servlet的生命周期

    Java中Servlet的生命周期

    這篇文章主要介紹了Java中Servlet的生命周期,Servlet?初始化后調(diào)用?init?()?方法、Servlet?調(diào)用?service()?方法來(lái)處理客戶端的請(qǐng)求、Servlet?銷毀前調(diào)用?destroy()?方法,下面來(lái)看看具體的解析吧,需要的小伙伴可以參考一下
    2022-01-01
  • Springboot集成spring data elasticsearch過(guò)程詳解

    Springboot集成spring data elasticsearch過(guò)程詳解

    這篇文章主要介紹了springboot集成spring data elasticsearch過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04

最新評(píng)論