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

最通俗的白話講解JDK源碼中的ThreadLocal

 更新時間:2022年01月20日 16:17:14   作者:慕楓技術筆記  
ThreadLocal是JDK包提供的,它提供線程本地變量,如果創(chuàng)建一樂ThreadLocal變量,那么訪問這個變量的每個線程都會有這個變量的一個副本,在實際多線程操作的時候,操作的是自己本地內存中的變量,從而規(guī)避了線程安全問題,感興趣的朋友快來看看吧

引言

其實網(wǎng)上有很多關于ThreadLocal的文章了,有不少文章也已經寫的非常好了。但是很多同學反應還有一些部分沒有講解的十分清楚,還是有一定的疑惑沒有想的十分清楚。因此本文主要結合常見的一些疑問、ThreadLocal源碼、應用實例以注意事項來全面而深入地再詳細講解一遍ThreadLocal。希望大家看完本文后可以徹底掌握ThreadLocal。

ThreadLocal是什么?它能干什么?

在闡述ThreadLocal之前,我們先來看下它的設計者是怎么描述ThreadLocal的吧。

看完官方的描述后,結合自己的理解,ThreadLocal提供了一種對應獨立線程內的數(shù)據(jù)訪問機制,實現(xiàn)了變量在線程之間隔離,在線程生命周期內獨立獲取或者設置的能力。如果我們想在線程內傳遞參數(shù)但是有不想作為方法參數(shù)的時候,ThreadLocal就可以排上用場了。不過值得注意的是ThreadLocal并不會解決變量共享問題。實際上從ThreadLocal的名稱上面來看,線程本地變量也已經大致說明了它的作用,所以變量的命名還是非常重要的,要做到顧名思義。如果覺得還不是很理解,沒關系,我們可以通過以下的場景再加深下理解。

假如有以下的場景,假設只有一個數(shù)據(jù)庫連接,客戶端1、2、3都需要獲取數(shù)據(jù)庫連接來進行具體的數(shù)據(jù)庫操作,但是同一時間點只能有一個線程獲取連接,其他線程只能等待。因此就會出現(xiàn)數(shù)據(jù)庫訪問效率不高的問題。

那我們有沒有什么辦法能夠避免線程等待的情況呢?上述問題的根本原因是數(shù)據(jù)庫連接是共享變量,同事只能有一個線程可以進行操作。那如果三個線程都有自己的數(shù)據(jù)庫連接,互相隔離,那不就不會出現(xiàn)等待的問題了嘛。那么此時我么可以使用ThreadLocal實現(xiàn)在不同線程中的變量隔離??梢钥闯鰜恚?code>ThreadLocal是一種已空間換取時間的做法。

ThreadLocal實現(xiàn)線程隔離的秘密

從上文中,我們了解到ThreadLocal可以實現(xiàn)變量訪問的線程級別的隔離。那么它是到底如何實現(xiàn)的呢?這還需要結合Thread以及ThreadLocal的源碼來分析才能揭開ThreadLocal實現(xiàn)線程隔離的神秘面紗。

public class Thread implements Runnable {
    ...
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ...
    
}

Thread源碼中我們發(fā)現(xiàn),它有一個threadLocals變量,它的類型是ThreadLocal中的內部類ThreadLocalMap。我們在看下ThreadLocalMap的定義是怎樣的。從源碼中我們可以看出來,ThreadLocalMap實際上就是Entry數(shù)組,這個Entry對應的key實際就是ThreadLocal的實例,value就是實際的變量值。

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

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
       ...
       //底層數(shù)據(jù)結構是數(shù)組
       private Entry[] table;
       ...
     
   }
  ...
  
}

通過查看上述的源碼,如果還不太好理解的話,我們再結合下現(xiàn)實中的例子來理解。大家都有支付寶賬戶,我們通過它來管理著我們的銀行卡、余額、花唄這些金融服務。

我們以支付寶以及支付寶賬戶進行類比,假設ThreadLocal就是支付寶,每個支付寶賬戶實際就是單獨的線程,而賬戶中的余額屬性就相當于Thread的私有屬性ThreadLocalMap。我們在日常生活中,進行賬戶余額的充值或者消費,并不是直接通過賬戶進行操作的,而是借助于支付寶進行維護的。這就相當于每個線程對ThreadLocalMap進行操作的時候也不是直接操作的,而是借助于ThreadLocal來操作。

那么Thread到底是怎么借助ThreadLocal進行私有屬性管理的呢?還是需要進一步查看Thread進行set以及get操作的源碼。從以下的ThreadLocal的源碼中我們可以看出,在進行操作之前,需要獲取當前的執(zhí)行操作的線程,再根據(jù)線程或者線程中私有的ThreadLocalMap屬性來進行操作。

在進行數(shù)據(jù)獲取的時候,也是按照同樣的流程,先獲取當前的線程,再獲取線程中對應的ThreadLocalMap屬性來進行后續(xù)的值的獲取。

經過上述的源碼的分析,我們可以得出這樣的結論,ThreadLocal之所以可以實現(xiàn)變量的線程隔離訪問,實際上就是借助于Thread中的ThreadLocalMap屬性來進行操作。由于都是操作線程本身的屬性,因此并不會影響其他線程中的變量值,因此可以實現(xiàn)線程級別的數(shù)據(jù)修改隔離。

為什么ThreadLocal會出現(xiàn)OOM的問題?

內存泄漏演示

我們都知道,ThreadLocal如果使用不當?shù)脑挄霈F(xiàn)內存泄漏的問題,那么我們就通過下面的這段代碼來分析下,內存泄漏的原因到底是什么。

/**
 * @author mufeng
 * @description 測試ThreadLocal內存溢出
 * @date 2022/1/16 19:01
 * @since
 */
public class ThreadLocalOOM {

    /**
     * 測試線程池
     */
    private static Executor threadPool = new ThreadPoolExecutor(3, 3, 40,
            TimeUnit.SECONDS, new LinkedBlockingDeque<>());


    static class Info {
        private byte[] info = new byte[10 * 1024 * 1024];
    }

    private  static ThreadLocal<Info> infoThreadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            threadPool.execute(() -> {
                infoThreadLocal.set(new Info());
                System.out.println("Thread started:" + Thread.currentThread().getName());
            });
            Thread.sleep(100);
        }

    }
}

手動進行GC之后,我們可以發(fā)現(xiàn)堆中仍然有超過30M的堆內存占用,如上面的代碼,在線程池中活躍的線程會有三個,對應的value為10M,說明在線程還存活的情況下,對應的value并沒有被回收,因此存在內存泄漏的情況,如果存在大量線程的情況,就會出現(xiàn)OOM。

當我們修改代碼在線程中進行remove操作,手動GC之后我們發(fā)現(xiàn)堆內存趨近于0了,之前沒有被回收的對象已經被回收了。

內存泄漏問題分析

以上是對于ThreadLocal發(fā)生內存泄漏問題的演示,那么再來仔細分析下背后的原因是什么。ThreadLocal中實際存儲數(shù)據(jù)的是ThreadLocalMap,實際上Map對應的key是一個虛引用,在GC的時候可以被回收掉,但是問題就在于key所對應的value,它是強引用,只要線程存活,那么這條引用鏈就會一致存在,如果出現(xiàn)大量線程的時候就會有OOM的風險。 所以在使用ThreadLocal的時候一定記得要顯式的調用remove方法進行清理,防止內存泄漏。

父子線程的參數(shù)傳遞

到這里,我相信大家對于ThreadLocal的原理有了比較深入的理解了。結合上文中的ThreadLocal代碼,不知道大家有沒有思考過一個問題,我們在使用ThreadLocal的時候都是在同一個線程內進行了set以及get操作,那么如果set操作與get操作在父子線程中是否還可以正常的獲取呢?帶著這樣的疑問,我們來看下如下的代碼。

/**
 * @author mufeng
 * @description 父子線程參數(shù)傳遞
 * @date 2022/1/16 9:54
 * @since
 */
public class InheritableThreadLocalMain {

    private static final ThreadLocal<String> count = new ThreadLocal<>();

    public static void main(String[] args) {

        count.set("父子線程參數(shù)傳遞!??!");
        System.out.println(Thread.currentThread().getName() + ":" + count.get());

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ":" + count.get());
        }).start();

    }

}

與之前代碼有所不同,ThreadLocal的設值是在main線程中進行的,但是獲取操作實際是在主線程下的子線程中進行的,大家可以分析一下運行結果是怎么樣的。

看到這個運行結果,不知道大家分析的對不對呢。實際上如果理解了上文的核心的話,這個問題應該很好分析的。ThreadLocal獲取數(shù)據(jù)的時候,首先是需要獲取當前的線程的,根據(jù)線程獲取實際存儲數(shù)據(jù)的ThreadLocalMap,上文代碼中設置和獲取在父子線程中進行,那肯定是獲取不到設置的數(shù)據(jù)的。但是在現(xiàn)實的項目開發(fā)中,我們會經常遇到需要將父線程的變量值傳遞給子線程進行處理,那么應該要怎么來實現(xiàn)呢?這個時候InheritableThreadLocal就派上用場了。

/**
 * @author mufeng
 * @description 父子線程參數(shù)傳遞
 * @date 2022/1/16 9:54
 * @since
 */
public class InheritableThreadLocalMain {

    private static final ThreadLocal<String> count = new InheritableThreadLocal<>();

    public static void main(String[] args) {

        count.set("父子線程參數(shù)傳遞?。?!");
        System.out.println(Thread.currentThread().getName() + ":" + count.get());

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ":" + count.get());
        }).start();

    }

}

那么InheritableThreadLocal到底是如何實現(xiàn)父子線程的參數(shù)傳遞的呢?我么還是的看看源碼中的實現(xiàn)原理。實際上在Thread源碼中,除了有Threadlocal私有屬性還有InheritableThreadLocal私有屬性。

public class Thread implements Runnable {
    
     /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
...
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }
    
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        ...
        //關鍵
         if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); 
        ...    
        
    }
    ...
    
}

實際在進行子線程創(chuàng)建的時候,在線程初始化過程中,判斷了父線程中的inheritableThreadLocals屬性是否為空,如果不為空的話需要進行值的復制,這樣便實現(xiàn)了父子線程的值傳遞。

總結

本文主要對ThreadLocal進行了相對全面的分析,從它的使用場景、原理以及源碼分析、產生OOM的原因以及一些使用上的注意,相信通過本文的學習,大家對于ThreadLocal會有更加深刻的理解。

到此這篇關于最通俗的白話講解JDK源碼中的ThreadLocal的文章就介紹到這了,更多相關Java ThreadLocal內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • java使用EasyExcel實現(xiàn)合并單元格

    java使用EasyExcel實現(xiàn)合并單元格

    這篇文章主要為大家詳細介紹了java使用EasyExcel實現(xiàn)合并單元格的相關知識,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2023-12-12
  • SpringBoot使用quartz,注入feignClient,client為null問題

    SpringBoot使用quartz,注入feignClient,client為null問題

    在SpringBoot中使用Quartz和FeignClient時,如果通過@Autowired或構造方法注入FeignClient導致為null,可以使用Spring提供的通過文件名獲取bean的方式解決,這種方法在Quartz啟動時通過反射注入類,而類還未初始化好,導致FeignClient為null的問題
    2024-11-11
  • java實現(xiàn)數(shù)據(jù)庫主鍵生成示例

    java實現(xiàn)數(shù)據(jù)庫主鍵生成示例

    這篇文章主要介紹了java實現(xiàn)數(shù)據(jù)庫主鍵生成示例,需要的朋友可以參考下
    2014-03-03
  • Java封裝、繼承、多態(tài)三大特征的理解

    Java封裝、繼承、多態(tài)三大特征的理解

    封裝、繼承、多態(tài)三大特征是java中比較常用的,務必要掌握,下面給大家介紹Java封裝、繼承、多態(tài)三大特征的理解,有不清楚的朋友可以一起學習下
    2016-06-06
  • java中ThreadLocal的基本原理

    java中ThreadLocal的基本原理

    本文講解了java中ThreadLocal的一些基本原理,文中關于ThreadLocal的原理講解的非常詳細,感興趣的朋友一起看看吧
    2021-08-08
  • Java中的List接口實現(xiàn)類解析

    Java中的List接口實現(xiàn)類解析

    這篇文章主要介紹了Java中的List接口實現(xiàn)類解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • 打包部署若依(RuoYi)SpringBoot后端和Vue前端圖文教程

    打包部署若依(RuoYi)SpringBoot后端和Vue前端圖文教程

    若依是一個使用Spring Boot作為后端和Vue.js作為前端的全棧應用開發(fā)平臺,下面這篇文章主要給大家介紹了關于打包部署若依(RuoYi)SpringBoot后端和Vue前端的相關資料,需要的朋友可以參考下
    2024-05-05
  • idea如何配置springboot熱部署

    idea如何配置springboot熱部署

    文章介紹了如何在不同版本的IntelliJ IDEA中配置靜態(tài)和動態(tài)編譯,并提供了觸發(fā)熱部署的方法
    2025-01-01
  • SpringBoot+Idea熱部署實現(xiàn)流程解析

    SpringBoot+Idea熱部署實現(xiàn)流程解析

    這篇文章主要介紹了SpringBoot+Idea熱部署實現(xiàn)流程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-11-11
  • 淺析Java?NIO?直接緩沖區(qū)和非直接緩沖區(qū)

    淺析Java?NIO?直接緩沖區(qū)和非直接緩沖區(qū)

    本篇文章主要為大家介紹了Java?NIO?中直接緩沖區(qū)和非直接緩沖區(qū)的定義以及使用流程,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-11-11

最新評論

宜丰县| 南靖县| 黄陵县| 陆河县| 浮山县| 阜平县| 长乐市| 阿巴嘎旗| 曲沃县| 南涧| 南丰县| 梅河口市| 呼玛县| 龙南县| 湘乡市| 永丰县| 右玉县| 绥阳县| 乌恰县| 大城县| 房产| 辽宁省| 大竹县| 富源县| 醴陵市| 常宁市| 哈巴河县| 布尔津县| 门头沟区| 文昌市| 山西省| 台东市| 南平市| 阆中市| 阳春市| 福海县| 巴林右旗| 巴马| 株洲县| 金堂县| 黔南|