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

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

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

ThreadLocal介紹

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

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

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

ThreadLocal 原理

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

每個Thread對象,專門用一個ThreadLocalMap來存儲自己的私有對象。ThreadLocalMap實際上就跟我們常用的HashMap類似,存儲在那里的Key-Value形式的數(shù)據(jù)。

ThreadLocal在每次獲取或設(shè)置操作時,都先通過Thread.currentThread()方法來獲取當(dāng)前線程,再從當(dāng)前線程中獲取ThreadLocalMap。而實際上,保存的值是通過ThreadLocalMap來存儲的。

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

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

ThreadLocalMap

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

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

ThreadLocal使用場景

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

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

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

全局存儲用戶信息

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

解決線程安全問題

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

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

ThreadLocal源碼

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

/** 
 * 返回當(dāng)前線程的 this 副本中的值 
 * 線程局部變量。如果變量沒有值 
 * 當(dāng)前線程,首先初始化為返回值 
 * 通過調(diào)用 {@link #initialValue} 方法。 
 * 
 * @return 這個線程本地的當(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è)置這個線程局部變量的當(dāng)前線程的副本 
 * 到指定值。大多數(shù)子類將不需要  
 * 重寫此方法,僅依賴于 {@link #initialValue} 
 * 設(shè)置線程局部變量值的方法。 
 * 
 * @param value 要存儲在當(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)前線程讀取,其值為 
 * 通過調(diào)用其 {@link #initialValue} 方法重新初始化, 
 * 除非它的值是當(dāng)前線程的 {@linkplain #set set} 
 * 在過渡期。這可能會導(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)存溢出問題

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

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

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadLocalOOMExample {
    /**
     * 定義一個 10m 大的類
     */
    static class MyTask {
        // 創(chuàng)建一個 10m 的數(shù)組(單位轉(zhuǎn)換是 1M -> 1024KB -> 1024*1024B)
        private byte[] bytes = new byte[10 * 1024 * 1024];
    }
    // 定義 ThreadLocal
    private static ThreadLocal<MyTask> taskThreadLocal = new ThreadLocal<>();
    // 主測試代碼
    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)建對象");
                // 創(chuàng)建對象(10M)
                MyTask myTask = new MyTask();
                // 存儲 ThreadLocal
                taskThreadLocal.set(myTask);
                // 將對象設(shè)置為 null,表示此對象不在使用了
                myTask = null;
            }
        });
    }
}

原因分析

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

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

解決方案

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

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class App {
    /**
     * 定義一個 10m 大的類
     */
    static class MyTask {
        // 創(chuàng)建一個 10m 的數(shù)組(單位轉(zhuǎn)換是 1M -> 1024KB -> 1024*1024B)
        private byte[] bytes = new byte[10 * 1024 * 1024];
    }
    // 定義 ThreadLocal
    private static ThreadLocal<MyTask> taskThreadLocal = new ThreadLocal<>();
    // 測試代碼
    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)建對象");
                try {
                    // 創(chuàng)建對象(10M)
                    MyTask myTask = new MyTask();
                    // 存儲 ThreadLocal
                    taskThreadLocal.set(myTask);
                    // 其他業(yè)務(wù)代碼...
                } finally {
                    // 釋放內(nèi)存
                    taskThreadLocal.remove();
                }
            }
        });
    }
}

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

相關(guān)文章

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

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

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

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

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

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

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

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

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

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

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

    JPA中EntityListeners注解的使用詳解

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

    springMVC攔截器HandlerInterceptor用法代碼示例

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

    springboot啟動mongoDB報錯之禁用mongoDB自動配置問題

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

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

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

    Java線程隊列LinkedBlockingQueue的使用

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

最新評論