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

Java面試必問(wèn)之ThreadLocal終極篇分享

 更新時(shí)間:2021年10月08日 12:03:21   作者:占小狼  
ThreadLocal是什么呢?其實(shí)ThreadLocal并非是一個(gè)線程的本地實(shí)現(xiàn)版本,它并不是一個(gè)Thread,而是thread local variable(線程局部變量),這篇文章主要給大家介紹了關(guān)于Java面試必問(wèn)之ThreadLocal終極篇的相關(guān)資料,需要的朋友可以參考下

前言

在面試環(huán)節(jié)中,考察"ThreadLocal"也是面試官的家常便飯,所以對(duì)它理解透徹,是非常有必要的.
有些面試官會(huì)開(kāi)門(mén)見(jiàn)山的提問(wèn):

  • “知道ThreadLocal嗎?”
  • “講講你對(duì)ThreadLocal的理解”

當(dāng)然了,也有面試官會(huì)慢慢引導(dǎo)到這個(gè)話題上,比如提問(wèn)“在多線程環(huán)境下,如何防止自己的變量被其它線程篡改”,將主動(dòng)權(quán)交給你自己,剩下的靠自己發(fā)揮。

那么ThreadLocal可以做什么,在了解它的應(yīng)用場(chǎng)景之前,我們先看看它的實(shí)現(xiàn)原理,只有知道了實(shí)現(xiàn)原理,才好判斷它是否符合自己的業(yè)務(wù)場(chǎng)景。

ThreadLocal是什么

首先,它是一個(gè)數(shù)據(jù)結(jié)構(gòu),有點(diǎn)像HashMap,可以保存"key : value"鍵值對(duì),但是一個(gè)ThreadLocal只能保存一個(gè),并且各個(gè)線程的數(shù)據(jù)互不干擾。

ThreadLocal<String> localName = new ThreadLocal();
localName.set("占小狼");
String name = localName.get();

在線程1中初始化了一個(gè)ThreadLocal對(duì)象localName,并通過(guò)set方法,保存了一個(gè)值占小狼,同時(shí)在線程1中通過(guò)localName.get()可以拿到之前設(shè)置的值,但是如果在線程2中,拿到的將是一個(gè)null。

這是為什么,如何實(shí)現(xiàn)?不過(guò)之前也說(shuō)了,ThreadLocal保證了各個(gè)線程的數(shù)據(jù)互不干擾。

看看set(T value)和get()方法的源碼

 public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

可以發(fā)現(xiàn),每個(gè)線程中都有一個(gè)ThreadLocalMap數(shù)據(jù)結(jié)構(gòu),當(dāng)執(zhí)行set方法時(shí),其值是保存在當(dāng)前線程的threadLocals變量中,當(dāng)執(zhí)行set方法中,是從當(dāng)前線程的threadLocals變量獲取。

所以在線程1中set的值,對(duì)線程2來(lái)說(shuō)是摸不到的,而且在線程2中重新set的話,也不會(huì)影響到線程1中的值,保證了線程之間不會(huì)相互干擾。

那每個(gè)線程中的ThreadLoalMap究竟是什么?

ThreadLoalMap

本文分析的是1.7的源碼。

從名字上看,可以猜到它也是一個(gè)類(lèi)似HashMap的數(shù)據(jù)結(jié)構(gòu),但是在ThreadLocal中,并沒(méi)實(shí)現(xiàn)Map接口。

在ThreadLoalMap中,也是初始化一個(gè)大小16的Entry數(shù)組,Entry對(duì)象用來(lái)保存每一個(gè)key-value鍵值對(duì),只不過(guò)這里的key永遠(yuǎn)都是ThreadLocal對(duì)象,是不是很神奇,通過(guò)ThreadLocal對(duì)象的set方法,結(jié)果把ThreadLocal對(duì)象自己當(dāng)做key,放進(jìn)了ThreadLoalMap中。

這里需要注意的是,ThreadLoalMap的Entry是繼承WeakReference,和HashMap很大的區(qū)別是,Entry中沒(méi)有next字段,所以就不存在鏈表的情況了。

hash沖突

沒(méi)有鏈表結(jié)構(gòu),那發(fā)生hash沖突了怎么辦?

先看看ThreadLoalMap中插入一個(gè)key-value的實(shí)現(xiàn)

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

每個(gè)ThreadLocal對(duì)象都有一個(gè)hash值threadLocalHashCode,每初始化一個(gè)ThreadLocal對(duì)象,hash值就增加一個(gè)固定的大小0x61c88647。

在插入過(guò)程中,根據(jù)ThreadLocal對(duì)象的hash值,定位到table中的位置i,過(guò)程如下:

1、如果當(dāng)前位置是空的,那么正好,就初始化一個(gè)Entry對(duì)象放在位置i上;

2、不巧,位置i已經(jīng)有Entry對(duì)象了,如果這個(gè)Entry對(duì)象的key正好是即將設(shè)置的key,那么重新設(shè)置Entry中的value;

3、很不巧,位置i的Entry對(duì)象,和即將設(shè)置的key沒(méi)關(guān)系,那么只能找下一個(gè)空位置;

這樣的話,在get的時(shí)候,也會(huì)根據(jù)ThreadLocal對(duì)象的hash值,定位到table中的位置,然后判斷該位置Entry對(duì)象中的key是否和get的key一致,如果不一致,就判斷下一個(gè)位置

可以發(fā)現(xiàn),set和get如果沖突嚴(yán)重的話,效率很低,因?yàn)門(mén)hreadLoalMap是Thread的一個(gè)屬性,所以即使在自己的代碼中控制了設(shè)置的元素個(gè)數(shù),但還是不能控制其它代碼的行為。

內(nèi)存泄露

ThreadLocal可能導(dǎo)致內(nèi)存泄漏,為什么?

先看看Entry的實(shí)現(xiàn):

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

通過(guò)之前的分析已經(jīng)知道,當(dāng)使用ThreadLocal保存一個(gè)value時(shí),會(huì)在ThreadLocalMap中的數(shù)組插入一個(gè)Entry對(duì)象,按理說(shuō)key-value都應(yīng)該以強(qiáng)引用保存在Entry對(duì)象中,但在ThreadLocalMap的實(shí)現(xiàn)中,key被保存到了WeakReference對(duì)象中。

這就導(dǎo)致了一個(gè)問(wèn)題,ThreadLocal在沒(méi)有外部強(qiáng)引用時(shí),發(fā)生GC時(shí)會(huì)被回收,如果創(chuàng)建ThreadLocal的線程一直持續(xù)運(yùn)行,那么這個(gè)Entry對(duì)象中的value就有可能一直得不到回收,發(fā)生內(nèi)存泄露。

如何避免內(nèi)存泄露

既然已經(jīng)發(fā)現(xiàn)有內(nèi)存泄露的隱患,自然有應(yīng)對(duì)的策略,在調(diào)用ThreadLocal的get()、set()可能會(huì)清除ThreadLocalMap中key為null的Entry對(duì)象,這樣對(duì)應(yīng)的value就沒(méi)有GC Roots可達(dá)了,下次GC的時(shí)候就可以被回收,當(dāng)然如果調(diào)用remove方法,肯定會(huì)刪除對(duì)應(yīng)的Entry對(duì)象。

如果使用ThreadLocal的set方法之后,沒(méi)有顯示的調(diào)用remove方法,就有可能發(fā)生內(nèi)存泄露,所以養(yǎng)成良好的編程習(xí)慣十分重要,使用完ThreadLocal之后,記得調(diào)用remove方法。

ThreadLocal<String> localName = new ThreadLocal();
try {
    localName.set("占小狼");
    // 其它業(yè)務(wù)邏輯
} finally {
    localName.remove();
}

總結(jié)

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

相關(guān)文章

  • Java線程池使用與原理詳解

    Java線程池使用與原理詳解

    這篇文章主要為大家詳細(xì)介紹了Java線程池使用與原理的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • SpringBoot動(dòng)態(tài)定時(shí)任務(wù)實(shí)現(xiàn)與應(yīng)用詳解

    SpringBoot動(dòng)態(tài)定時(shí)任務(wù)實(shí)現(xiàn)與應(yīng)用詳解

    定時(shí)任務(wù)在許多應(yīng)用場(chǎng)景中是必不可少的,特別是在自動(dòng)化任務(wù)執(zhí)行、定期數(shù)據(jù)處理等方面,定時(shí)任務(wù)能極大地提高系統(tǒng)的效率,然而,隨著業(yè)務(wù)需求的變化,定時(shí)任務(wù)的執(zhí)行頻率或時(shí)間點(diǎn)可能需要?jiǎng)討B(tài)調(diào)整,所以本文給大家介紹了SpringBoot動(dòng)態(tài)定時(shí)任務(wù)實(shí)現(xiàn)與應(yīng)用
    2024-08-08
  • Java8新特性之Stream使用詳解

    Java8新特性之Stream使用詳解

    這篇文章主要介紹了Java8新特性之Stream使用詳解,流是用來(lái)處理集合中的數(shù)據(jù),以聲明的形式操作集合,它就像SQL語(yǔ)句,我們只需告訴流需要對(duì)集合進(jìn)行什么操作,它就會(huì)自動(dòng)進(jìn)行操作,并將執(zhí)行結(jié)果交給你,無(wú)需我們自己手寫(xiě)代碼,需要的朋友可以參考下
    2023-08-08
  • springboot自定義校驗(yàn)注解的實(shí)現(xiàn)過(guò)程

    springboot自定義校驗(yàn)注解的實(shí)現(xiàn)過(guò)程

    這篇文章主要介紹了springboot自定義校驗(yàn)注解的實(shí)現(xiàn)過(guò)程,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2023-11-11
  • SpringBoot整合郵件發(fā)送的四種方法

    SpringBoot整合郵件發(fā)送的四種方法

    這篇文章主要介紹了SpringBoot整合郵件發(fā)送的四種方法,SpringBoot中集成了發(fā)送郵件的功能,本文做了進(jìn)一步優(yōu)化,需要的朋友可以參考下
    2023-03-03
  • 一篇文章帶你入門(mén)Springboot整合微信登錄與微信支付(附源碼)

    一篇文章帶你入門(mén)Springboot整合微信登錄與微信支付(附源碼)

    微信支付是騰訊公司的支付業(yè)務(wù)品牌,微信支付商戶平臺(tái)支持線下場(chǎng)所、公眾號(hào)、小程序、PC網(wǎng)站、APP、企業(yè)微信等經(jīng)營(yíng)場(chǎng)景快速接入微信支付。這里一篇文章帶你入門(mén)!
    2021-06-06
  • Windows環(huán)境使用bat腳本啟動(dòng)Java服務(wù)的過(guò)程

    Windows環(huán)境使用bat腳本啟動(dòng)Java服務(wù)的過(guò)程

    Java項(xiàng)目一般會(huì)被打包成jar后啟動(dòng),在windows系統(tǒng)中可以通過(guò)終端窗口cmd啟動(dòng)jar包,即在jar包所在的目錄中打開(kāi)cmd,或在cmd中進(jìn)入到j(luò)ar包目錄,這篇文章主要介紹了Windows環(huán)境使用bat腳本啟動(dòng)Java服務(wù),需要的朋友可以參考下
    2023-08-08
  • 淺談BeanPostProcessor加載次序及其對(duì)Bean造成的影響分析

    淺談BeanPostProcessor加載次序及其對(duì)Bean造成的影響分析

    這篇文章主要介紹了淺談BeanPostProcessor加載次序及其對(duì)Bean造成的影響分析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • Java中4種校驗(yàn)注解詳解(值校驗(yàn)、范圍校驗(yàn)、長(zhǎng)度校驗(yàn)、格式校驗(yàn))

    Java中4種校驗(yàn)注解詳解(值校驗(yàn)、范圍校驗(yàn)、長(zhǎng)度校驗(yàn)、格式校驗(yàn))

    這篇文章主要給大家介紹了關(guān)于Java中4種校驗(yàn)注解詳解的相關(guān)資料,分別包括值校驗(yàn)、范圍校驗(yàn)、長(zhǎng)度校驗(yàn)、格式校驗(yàn)等,Java注解(Annotation)是一種元數(shù)據(jù),它可以被添加到Java代碼中,并可以提供額外的信息和指令,需要的朋友可以參考下
    2023-08-08
  • Java 8系列之Stream中萬(wàn)能的reduce用法說(shuō)明

    Java 8系列之Stream中萬(wàn)能的reduce用法說(shuō)明

    這篇文章主要介紹了Java 8系列之Stream中萬(wàn)能的reduce用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-08-08

最新評(píng)論