欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java線程中的ThreadLocal原理及源碼解析

 更新時(shí)間:2023年12月07日 10:19:18   作者:外星喵  
這篇文章主要介紹了Java線程中的ThreadLocal原理及源碼解析,ThreadLocal 的作用是為每個(gè)線程保存一份局部變量的引用,實(shí)現(xiàn)多線程之間的數(shù)據(jù)隔離,從而避免了線程不安全情況的發(fā)生,需要的朋友可以參考下

ThreadLocal介紹

ThreadLocal,線程本地變量,ThreadLocal 的作用是為每個(gè)線程保存一份局部變量的引用,實(shí)現(xiàn)多線程之間的數(shù)據(jù)隔離,從而避免了線程不安全情況的發(fā)生。這個(gè)變量保存的值只在線程的生命周期內(nèi)起作用,通過(guò)使用它減少了將執(zhí)行上下文信息傳遞到每個(gè)方法的需要。

如果多個(gè)線程同時(shí)在一個(gè)對(duì)象/實(shí)例上執(zhí)行,它們將共享這個(gè)實(shí)例變量,如果不使用ThreadLocal,就需要在每個(gè)方法上傳遞參數(shù),去跨對(duì)象共享這些變量,同時(shí)還會(huì)導(dǎo)致線程不安全的問(wèn)題。

許多框架使用 ThreadLocals 來(lái)維護(hù)與當(dāng)前線程相關(guān)的一些上下文。例如,當(dāng)前事務(wù)存儲(chǔ)在 ThreadLocal 中時(shí),您不需要通過(guò)每個(gè)方法調(diào)用將其作為參數(shù)傳遞,以防堆棧中的某個(gè)人需要訪問(wèn)它。Web 應(yīng)用程序可能會(huì)將有關(guān)當(dāng)前請(qǐng)求和會(huì)話的信息存儲(chǔ)在 ThreadLocal 中,以便應(yīng)用程序可以輕松訪問(wèn)它們。

ThreadLocal 原理

ThreadLocals 是一種全局變量(盡管由于它們僅限于一個(gè)線程而稍微不那么邪惡),因此在使用它們時(shí)應(yīng)該小心以避免不必要的副作用和內(nèi)存泄漏。

每個(gè)Thread對(duì)象,專門(mén)用一個(gè)ThreadLocalMap來(lái)存儲(chǔ)自己的私有對(duì)象。ThreadLocalMap實(shí)際上就跟我們常用的HashMap類似,存儲(chǔ)在那里的Key-Value形式的數(shù)據(jù)。

ThreadLocal在每次獲取或設(shè)置操作時(shí),都先通過(guò)Thread.currentThread()方法來(lái)獲取當(dāng)前線程,再?gòu)漠?dāng)前線程中獲取ThreadLocalMap。而實(shí)際上,保存的值是通過(guò)ThreadLocalMap來(lái)存儲(chǔ)的。

ThreadLocal對(duì)象可以是多線程共享,但ThreadLocalMap對(duì)象卻是一個(gè)線程獨(dú)享的,每個(gè)線程對(duì)象,創(chuàng)建一個(gè)自己專屬的ThreadLocalMap,與其他Thread對(duì)象創(chuàng)建的ThreadLocalMap不存在一個(gè)單一的關(guān)系。

當(dāng)多個(gè)Thread對(duì)象共同訪問(wèn)同一個(gè)ThreadLocal對(duì)象時(shí),threadLocal只是作為T(mén)hreadLocalMap的Key存在,而不是作為變量的存儲(chǔ)位置。threadLocal的set(方法和get()方法涉及的值是存儲(chǔ)為T(mén)hreadLocalMap的值而ThreadLocalMap是每個(gè)線程專屬的,互不相同的。這就是為什么同ThreadLocal被多線程同時(shí)訪問(wèn),ThreadLocal的值卻互不干擾的原理。

ThreadLocalMap

ThreadLocalMap該類的核心部分是Entry class,它擴(kuò)展了WeakReference. 它確保如果當(dāng)前線程退出,它將被自動(dòng)垃圾收集。這就是為什么它使用ThreadLocalMap而不是簡(jiǎn)單的HashMap. 它將當(dāng)前ThreadLocal及其值作為Entry類的參數(shù)傳遞,所以當(dāng)我們想要獲取值時(shí),我們可以從 中獲取它table.

  • 每個(gè)線程中都有一個(gè)自己的 ThreadLocalMap 類對(duì)象,可以將線程自己的對(duì)象保持到其中, 各管各的,線程可以正確的訪問(wèn)到自己的對(duì)象。
  • 將一個(gè)共用的 ThreadLocal 靜態(tài)實(shí)例作為 key,將不同對(duì)象的引用保存到不同線程的 ThreadLocalMap中,然后在線程執(zhí)行的各處通過(guò)這個(gè)靜態(tài)ThreadLocal實(shí)例的get()方法取 得自己線程保存的那個(gè)對(duì)象,避免了將這個(gè)對(duì)象作為參數(shù)傳遞的麻煩。
  • ThreadLocalMap其實(shí)就是線程里面的一個(gè)屬性,它在Thread類中定義
ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal使用場(chǎng)景

代替參數(shù)的顯式傳遞

當(dāng)我們?cè)趯?xiě)API接口的時(shí)候,通常Controller層會(huì)接受來(lái)自前端的入?yún)?,?dāng)這個(gè)接口功能比較復(fù)雜的時(shí)候,可能我們調(diào)用的Service層內(nèi)部還調(diào)用了 很多其他的很多方法,通常情況下,我們會(huì)在每個(gè)調(diào)用的方法上加上需要傳遞的參數(shù)。

但是如果我們將參數(shù)存入ThreadLocal中,那么就不用顯式的傳遞參數(shù)了,而是只需要ThreadLocal中獲取即可。

全局存儲(chǔ)用戶信息

在現(xiàn)在的系統(tǒng)設(shè)計(jì)中,前后端分離已基本成為常態(tài),分離之后如何獲取用戶信息就成了一件麻煩事,通常在用戶登錄后, 用戶信息會(huì)保存在Session或者Token中。這個(gè)時(shí)候,我們?nèi)绻褂贸R?guī)的手段去獲取用戶信息會(huì)很費(fèi)勁,拿Session來(lái)說(shuō),我們要在接口參數(shù)中加上HttpServletRequest對(duì)象,然后調(diào)用 getSession方法,且每一個(gè)需要用戶信息的接口都要加上這個(gè)參數(shù),才能獲取Session,這樣實(shí)現(xiàn)就很麻煩了。 當(dāng)請(qǐng)求到來(lái)時(shí),可以將當(dāng)前Session信息存儲(chǔ)在ThreadLocal中,在請(qǐng)求處理過(guò)程中可以隨時(shí)使用Session信息,每個(gè)請(qǐng)求之間的Session信息互不影響。當(dāng)請(qǐng)求處理完成后通過(guò)remove方法將當(dāng)前Session信息清除即可。

解決線程安全問(wèn)題

在Spring的Web項(xiàng)目中,我們通常會(huì)將業(yè)務(wù)分為Controller層,Service層,Dao層, 我們都知道@Autowired注解默認(rèn)使用單例模式,那么不同請(qǐng)求線程進(jìn)來(lái)之后,由于Dao層使用單例,那么負(fù)責(zé)數(shù)據(jù)庫(kù)連接的Connection也只有一個(gè), 如果每個(gè)請(qǐng)求線程都去連接數(shù)據(jù)庫(kù),那么就會(huì)造成線程不安全的問(wèn)題,Spring是如何解決這個(gè)問(wèn)題的呢?

在Spring項(xiàng)目中Dao層中裝配的Connection肯定是線程安全的,其解決方案就是采用ThreadLocal方法,當(dāng)每個(gè)請(qǐng)求線程使用Connection的時(shí)候, 都會(huì)從ThreadLocal獲取一次,如果為null,說(shuō)明沒(méi)有進(jìn)行過(guò)數(shù)據(jù)庫(kù)連接,連接后存入ThreadLocal中,如此一來(lái),每一個(gè)請(qǐng)求線程都保存有一份 自己的Connection。于是便解決了線程安全問(wèn)題

ThreadLocal源碼

以下是ThreadLocal的get()、set()、remove()方法的代碼

/** 
 * 返回當(dāng)前線程的 this 副本中的值 
 * 線程局部變量。如果變量沒(méi)有值 
 * 當(dāng)前線程,首先初始化為返回值 
 * 通過(guò)調(diào)用 {@link #initialValue} 方法。 
 * 
 * @return 這個(gè)線程本地的當(dāng)前線程的值 
 */  
public T get() {  
    Thread t = Thread.currentThread();  
    ThreadLocalMap map = getMap(t);  
    if (map != null) {  
        ThreadLocalMap.Entry e = map.getEntry(this);  
        if (e != null)  
            return (T)e.value;  
    }  
    return setInitialValue();  
}  
/** 
 * 設(shè)置這個(gè)線程局部變量的當(dāng)前線程的副本 
 * 到指定值。大多數(shù)子類將不需要  
 * 重寫(xiě)此方法,僅依賴于 {@link #initialValue} 
 * 設(shè)置線程局部變量值的方法。 
 * 
 * @param value 要存儲(chǔ)在當(dāng)前線程的副本中的值。
 */  
public void set(T value) {  
    Thread t = Thread.currentThread();  
    ThreadLocalMap map = getMap(t);  
    if (map != null)  
        map.set(this, value);  
    else  
        createMap(t, value);  
}  
/** 
 * 刪除此線程本地的當(dāng)前線程的值 
 * 多變的。如果此線程局部變量隨后 
 * {@linkplain #get read} 被當(dāng)前線程讀取,其值為 
 * 通過(guò)調(diào)用其 {@link #initialValue} 方法重新初始化, 
 * 除非它的值是當(dāng)前線程的 {@linkplain #set set} 
 * 在過(guò)渡期。這可能會(huì)導(dǎo)致多次調(diào)用 
 * 當(dāng)前線程中的 <tt>initialValue</tt> 方法。 
 * 
 * @自 1.5 
 */  
 public void remove() {  
     ThreadLocalMap m = getMap(Thread.currentThread());  
     if (m != null)  
         m.remove(this);  
 }  

ThreadLocal內(nèi)存溢出問(wèn)題

內(nèi)存溢出問(wèn)題模擬

在執(zhí)行main方法前,先使用“-Xmx50m”的參數(shù)來(lái)配置一下 Idea,它表示將程序運(yùn)行的最大內(nèi)存設(shè)置為 50m,如果程序的運(yùn)行超過(guò)這個(gè)值就會(huì)出現(xiàn)內(nèi)存溢出的問(wèn)題

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadLocalOOMExample {
    /**
     * 定義一個(gè) 10m 大的類
     */
    static class MyTask {
        // 創(chuàng)建一個(gè) 10m 的數(shù)組(單位轉(zhuǎn)換是 1M -> 1024KB -> 1024*1024B)
        private byte[] bytes = new byte[10 * 1024 * 1024];
    }
    // 定義 ThreadLocal
    private static ThreadLocal<MyTask> taskThreadLocal = new ThreadLocal<>();
    // 主測(cè)試代碼
    public static void main(String[] args) throws InterruptedException {
        // 創(chuàng)建線程池
        ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(5, 5, 60,
                        TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));
        // 執(zhí)行 10 次調(diào)用
        for (int i = 0; i < 10; i++) {
            // 執(zhí)行任務(wù)
            executeTask(threadPoolExecutor);
            Thread.sleep(1000);
        }
    }
    /**
     * 線程池執(zhí)行任務(wù)
     * @param threadPoolExecutor 線程池
     */
    private static void executeTask(ThreadPoolExecutor threadPoolExecutor) {
        // 執(zhí)行任務(wù)
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("創(chuàng)建對(duì)象");
                // 創(chuàng)建對(duì)象(10M)
                MyTask myTask = new MyTask();
                // 存儲(chǔ) ThreadLocal
                taskThreadLocal.set(myTask);
                // 將對(duì)象設(shè)置為 null,表示此對(duì)象不在使用了
                myTask = null;
            }
        });
    }
}

原因分析

由于每個(gè)線程 Thread 都擁有一個(gè)數(shù)據(jù)存儲(chǔ)容器 ThreadLocalMap,當(dāng)執(zhí)行 ThreadLocal.set 方法執(zhí)行時(shí),會(huì)將要存儲(chǔ)的值放到 ThreadLocalMap 容器中。而ThreadMap 中有一個(gè) Entry[] 數(shù)組用來(lái)存儲(chǔ)所有的數(shù)據(jù),而 Entry 是一個(gè)包含 key 和 value 的鍵值對(duì),其中 key 為 ThreadLocal 本身,而 value 則是要存儲(chǔ)在 ThreadLocal 中的值。

也就是說(shuō)它們之間的引用關(guān)系是這樣的:Thread -> ThreadLocalMap -> Entry -> Key,Value,因此當(dāng)我們使用線程池來(lái)存儲(chǔ)對(duì)象時(shí),因?yàn)榫€程池有很長(zhǎng)的生命周期,所以線程池會(huì)一直持有 value 值,那么垃圾回收器就無(wú)法回收 value,所以就會(huì)導(dǎo)致內(nèi)存一直被占用,從而導(dǎo)致內(nèi)存溢出問(wèn)題的發(fā)生。

解決方案

嚴(yán)格來(lái)講內(nèi)存溢出并不是 ThreadLocal 的問(wèn)題,而是因?yàn)闆](méi)有正確使用 ThreadLocal 所帶來(lái)的問(wèn)題。想要避免 ThreadLocal 內(nèi)存溢出的問(wèn)題,只需要在使用完 ThreadLocal 后調(diào)用 remove 方法即可。

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class App {
    /**
     * 定義一個(gè) 10m 大的類
     */
    static class MyTask {
        // 創(chuàng)建一個(gè) 10m 的數(shù)組(單位轉(zhuǎn)換是 1M -> 1024KB -> 1024*1024B)
        private byte[] bytes = new byte[10 * 1024 * 1024];
    }
    // 定義 ThreadLocal
    private static ThreadLocal<MyTask> taskThreadLocal = new ThreadLocal<>();
    // 測(cè)試代碼
    public static void main(String[] args) throws InterruptedException {
        // 創(chuàng)建線程池
        ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(5, 5, 60,
                        TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));
        // 執(zhí)行 n 次調(diào)用
        for (int i = 0; i < 10; i++) {
            // 執(zhí)行任務(wù)
            executeTask(threadPoolExecutor);
            Thread.sleep(1000);
        }
    }
    /**
     * 線程池執(zhí)行任務(wù)
     * @param threadPoolExecutor 線程池
     */
    private static void executeTask(ThreadPoolExecutor threadPoolExecutor) {
        // 執(zhí)行任務(wù)
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("創(chuàng)建對(duì)象");
                try {
                    // 創(chuàng)建對(duì)象(10M)
                    MyTask myTask = new MyTask();
                    // 存儲(chǔ) ThreadLocal
                    taskThreadLocal.set(myTask);
                    // 其他業(yè)務(wù)代碼...
                } finally {
                    // 釋放內(nèi)存
                    taskThreadLocal.remove();
                }
            }
        });
    }
}

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

相關(guān)文章

  • Java 內(nèi)部類的定義與范例

    Java 內(nèi)部類的定義與范例

    說(shuō)起內(nèi)部類這個(gè)詞,想必很多人都不陌生,但是又會(huì)覺(jué)得不熟悉。原因是平時(shí)編寫(xiě)代碼時(shí)可能用到的場(chǎng)景不多,用得最多的是在有事件監(jiān)聽(tīng)的情況下,并且即使用到也很少去總結(jié)內(nèi)部類的用法。今天我們就來(lái)一探究竟
    2021-11-11
  • Java方法簽名為何不包含返回值類型

    Java方法簽名為何不包含返回值類型

    這篇文章主要介紹了Java方法簽名為何不包含返回值類型,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06
  • java中Hutool包的常用方法總結(jié)

    java中Hutool包的常用方法總結(jié)

    這篇文章主要為大家詳細(xì)介紹了java在工作中中Hutool包的一些常用方法總結(jié),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-12-12
  • Java如何使用poi生成簡(jiǎn)單word文檔并導(dǎo)出

    Java如何使用poi生成簡(jiǎn)單word文檔并導(dǎo)出

    這篇文章主要介紹了Java如何使用poi生成簡(jiǎn)單word文檔并導(dǎo)出問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • SpringCloud?Eureka服務(wù)注冊(cè)中心應(yīng)用入門(mén)詳解

    SpringCloud?Eureka服務(wù)注冊(cè)中心應(yīng)用入門(mén)詳解

    這篇文章主要介紹了Spring?Cloud?Eureka服務(wù)注冊(cè)中心入門(mén)流程分析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-07-07
  • JPA中EntityListeners注解的使用詳解

    JPA中EntityListeners注解的使用詳解

    這篇文章主要介紹了JPA中EntityListeners注解的使用詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-01-01
  • springMVC攔截器HandlerInterceptor用法代碼示例

    springMVC攔截器HandlerInterceptor用法代碼示例

    這篇文章主要介紹了springMVC攔截器HandlerInterceptor用法代碼示例,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2017-12-12
  • springboot啟動(dòng)mongoDB報(bào)錯(cuò)之禁用mongoDB自動(dòng)配置問(wèn)題

    springboot啟動(dòng)mongoDB報(bào)錯(cuò)之禁用mongoDB自動(dòng)配置問(wèn)題

    這篇文章主要介紹了springboot啟動(dòng)mongoDB報(bào)錯(cuò)之禁用mongoDB自動(dòng)配置問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • springboot 2.3之后消失的hibernate-validator解決方法

    springboot 2.3之后消失的hibernate-validator解決方法

    這篇文章主要介紹了springboot 2.3之后消失的hibernate-validator解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • Java線程隊(duì)列LinkedBlockingQueue的使用

    Java線程隊(duì)列LinkedBlockingQueue的使用

    本文主要介紹了Java線程隊(duì)列LinkedBlockingQueue的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06

最新評(píng)論