溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

java中InheritableThreadLocal異步傳遞數(shù)據(jù)的實(shí)現(xiàn)原理

發(fā)布時(shí)間:2021-09-14 17:02:04 來源:億速云 閱讀:134 作者:chen 欄目:web開發(fā)

本篇內(nèi)容主要講解“java中InheritableThreadLocal異步傳遞數(shù)據(jù)的實(shí)現(xiàn)原理”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“java中InheritableThreadLocal異步傳遞數(shù)據(jù)的實(shí)現(xiàn)原理”吧!

在Java中,一個(gè)Java線程就是一個(gè)操作系統(tǒng)線程,創(chuàng)建一個(gè)線程需要通過new  Thread創(chuàng)建,由JVM為Thread綁定操作系統(tǒng)線程,即便是使用線程池,也需要通過new Thread創(chuàng)建線程。

Thread類有兩個(gè)ThreadLocal字段:

public class Thread implements Runnable {     ThreadLocal.ThreadLocalMap threadLocals = null;     ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; }

InheritableThreadLocal是ThreadLocal的子類,本質(zhì)上就是一個(gè)ThreadLocal。

在Thread類中,threadLocals與inheritableThreadLocals都是線程對象私有的,只能通過當(dāng)前線程對象寫入和獲取數(shù)據(jù),只是Thread會(huì)將寫入inheritableThreadLocals的數(shù)據(jù)傳遞給子線程的inheritableThreadLocals。

當(dāng)我們往ThreadLocal或者InheritableThreadLocal寫入數(shù)據(jù)時(shí),寫入過程為:

  • 1、ThreadLocal或者InheritableThreadLocal先調(diào)用Thread#currentThread靜態(tài)方法獲取當(dāng)前線程的Thread對象;

  • 2、獲取Thread對象的threadLocals或者inheritableThreadLocals;

  • 3、將ThreadLocal或者InheritableThreadLocal對象作為key,將數(shù)據(jù)寫入到當(dāng)前Thread對象的threadLocals或者inheritableThreadLocals字段中。

因此,Thread的threadLocals與inheritableThreadLocals的key是ThreadLocal或者InheritableThreadLocal實(shí)例,value是寫入的數(shù)據(jù)。

關(guān)于threadLocals我在前面一篇《反向理解ThreadLocal,或許這樣更容易理解》已經(jīng)詳細(xì)介紹過了,本篇重點(diǎn)分析inheritableThreadLocals是如何傳遞給子線程的。

默認(rèn)情況下,當(dāng)我們使用new  Thread()創(chuàng)建一個(gè)線程時(shí),在Thread的構(gòu)造方法中會(huì)通過Thread#currentThread獲取當(dāng)前線程,將當(dāng)前線程作為新創(chuàng)建線程的父線程,所以就有了父子線程關(guān)系。

無論使用哪個(gè)重載的構(gòu)造方法創(chuàng)建Thread,都會(huì)在構(gòu)造方法中調(diào)用init方法完成初始化為Thread字段賦值,而init方法中有這樣一段代碼:

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);        ...... }

在init方法中,由于inheritThreadLocals參數(shù)默認(rèn)為true,所以只要父線程的inheritableThreadLocals字段不為空,就copy一份父線程的inheritableThreadLocals給當(dāng)前創(chuàng)建的線程對象,這就實(shí)現(xiàn)了將父線程的inheritableThreadLocals存儲(chǔ)的數(shù)據(jù)傳遞給子線程。

使用InheritableThreadLocal我們不得不考慮的問題:內(nèi)存泄漏。

ThreadLocal.ThreadLocalMap使用數(shù)組存儲(chǔ)元素,與HashMap不同,它通過開放定址法解決hash沖突,不存在鏈表,通過動(dòng)態(tài)擴(kuò)容數(shù)組可無限存儲(chǔ)元素,數(shù)組元素的類型為Entry。

當(dāng)我們往ThreadLocal.ThreadLocalMap寫入一個(gè)key-value時(shí),ThreadLocalMap把key和value包裝成一個(gè)Entry,并通過key的hashcode值計(jì)算索引值,將Entry放到數(shù)組中。

ThreadLocal.ThreadLocalMap.Entry類的源碼如下:

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

雖然key為弱引用的ThreadLocal,當(dāng)ThreadLocal釋放時(shí),Entry的key變?yōu)閚ull,但由于value還在,如果Thread不釋放,那么Entry也就不會(huì)被垃圾收集器回收。

但如果線程是臨時(shí)創(chuàng)建的,在方法中創(chuàng)建且沒有被其它地方引用,當(dāng)線程執(zhí)行完成時(shí)就會(huì)被JVM銷毀,在線程實(shí)際退出之前由JVM調(diào)用線程的exit方法給線程對象完成清理。exit方法部分源碼如下。

private void exit() {     ......     threadLocals = null;     inheritableThreadLocals = null;     ...... }

因此,只要Thread對象的exit方法被調(diào)用,就不會(huì)存在內(nèi)存泄漏問題。只要線程用完就銷毀,那么使用InheritableThreadLocal,在子線程中不需要調(diào)用InheritableThreadLocal的remove方法也不會(huì)存在內(nèi)存泄漏的可能。

比如我們在項(xiàng)目中使用InheritableThreadLocal實(shí)現(xiàn)將Session傳遞給子線程:

@GetMapping("/test") public SsoUser test() {     // 獲取登錄用戶     SsoUser ssoUser = SsoUserManager.curLoggedUser();     System.out.println(ssoUser.getUserCode());     // 支持子線程傳遞     new Thread(() -> {         try {             Thread.sleep(100);             SsoUser ssoUser2 = SsoUserManager.curLoggedUser();             System.out.println(ssoUser2.getUserCode());         } catch (InterruptedException e) {         }     }).start();     return ssoUser; }

在此案例中,由于子線程只是臨時(shí)創(chuàng)建的,所以我們不需要在子線程中調(diào)用InheritableThreadLocal的remove方法,只需要在父線程調(diào)用一次remove方法,因?yàn)閠omcat的work線程是不會(huì)在一次請求結(jié)束后就銷毀的。

現(xiàn)在我們已經(jīng)知道了InheritableThreadLocal是如何實(shí)現(xiàn)將數(shù)據(jù)傳遞給子線程的,思考題的答案也就有了一半:由于InheritableThreadLocal只能將線程上下文傳遞給當(dāng)前線程創(chuàng)建的子線程,所以只有線程池中的線程是由當(dāng)前線程創(chuàng)建的才能夠傳遞。

但要知道另一半答案我們還需要從線程池中尋找。

使用不同參數(shù)構(gòu)建的線程池不同,常見的有單線程的線程池、只有固定數(shù)量核心線程的線程池、有固定數(shù)量核心線程和非核心線程的線程池、只有非核心線程的線程池。

線程池的幾個(gè)構(gòu)造參數(shù)說明如下:

  • corePoolSize:核心線程數(shù),不會(huì)被釋放的線程數(shù)量(設(shè)置allowCoreThreadTimeOut為ture時(shí)例外);

  • maximumPoolSize:線程池的最大線程數(shù),等于核心線程與非核心線程的數(shù)量總和;

  • keepAliveTime:非核心線程最大空閑等待時(shí)間,在指定空閑時(shí)間后如果還沒有任務(wù)則釋放該線程;

  • workQueue:任務(wù)隊(duì)列,當(dāng)核心線程數(shù)用完時(shí),任務(wù)被放入隊(duì)列。

一、線程池是臨時(shí)線程池

如果線程池是在當(dāng)前線程創(chuàng)建的,且任務(wù)都是由當(dāng)前線程提交的,線程池用完就消毀了,那么不管是哪種線程池,池中的線程都是由當(dāng)前線程所創(chuàng)建,在這種場景下,InheritableThreadLocal能夠?qū)ontext傳給給線程池中的任一線程。

二、線程池是全局線程池

如果線程池是全局線程池:

  • 沒有核心線程且非核心線程的keepAliveTime等于0:線程都是用到才創(chuàng)建,且由于keepAliveTime等于0,線程用完可能就釋放了,在這種場景下,相當(dāng)于是由當(dāng)前線程創(chuàng)建子線程執(zhí)行任務(wù),因此能夠?qū)崿F(xiàn)透傳;

  • 沒有非核心線程:前(核心線程數(shù))個(gè)任務(wù)的提交都會(huì)創(chuàng)建線程,也都是由當(dāng)前線程創(chuàng)建,所以只有這幾個(gè)任務(wù)的執(zhí)行是能夠正常獲取父線程寫入InheritableThreadLocal的數(shù)據(jù)的,后面提交的任務(wù)就不知道會(huì)被哪個(gè)核心線程拉取執(zhí)行了;

到此,相信大家對“java中InheritableThreadLocal異步傳遞數(shù)據(jù)的實(shí)現(xiàn)原理”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI