您好,登錄后才能下訂單哦!
本篇內容主要講解“java中InheritableThreadLocal異步傳遞數據的實現原理”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“java中InheritableThreadLocal異步傳遞數據的實現原理”吧!
在Java中,一個Java線程就是一個操作系統線程,創建一個線程需要通過new Thread創建,由JVM為Thread綁定操作系統線程,即便是使用線程池,也需要通過new Thread創建線程。
Thread類有兩個ThreadLocal字段:
public class Thread implements Runnable { ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; }
InheritableThreadLocal是ThreadLocal的子類,本質上就是一個ThreadLocal。
在Thread類中,threadLocals與inheritableThreadLocals都是線程對象私有的,只能通過當前線程對象寫入和獲取數據,只是Thread會將寫入inheritableThreadLocals的數據傳遞給子線程的inheritableThreadLocals。
當我們往ThreadLocal或者InheritableThreadLocal寫入數據時,寫入過程為:
1、ThreadLocal或者InheritableThreadLocal先調用Thread#currentThread靜態方法獲取當前線程的Thread對象;
2、獲取Thread對象的threadLocals或者inheritableThreadLocals;
3、將ThreadLocal或者InheritableThreadLocal對象作為key,將數據寫入到當前Thread對象的threadLocals或者inheritableThreadLocals字段中。
因此,Thread的threadLocals與inheritableThreadLocals的key是ThreadLocal或者InheritableThreadLocal實例,value是寫入的數據。
關于threadLocals我在前面一篇《反向理解ThreadLocal,或許這樣更容易理解》已經詳細介紹過了,本篇重點分析inheritableThreadLocals是如何傳遞給子線程的。
默認情況下,當我們使用new Thread()創建一個線程時,在Thread的構造方法中會通過Thread#currentThread獲取當前線程,將當前線程作為新創建線程的父線程,所以就有了父子線程關系。
無論使用哪個重載的構造方法創建Thread,都會在構造方法中調用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參數默認為true,所以只要父線程的inheritableThreadLocals字段不為空,就copy一份父線程的inheritableThreadLocals給當前創建的線程對象,這就實現了將父線程的inheritableThreadLocals存儲的數據傳遞給子線程。
使用InheritableThreadLocal我們不得不考慮的問題:內存泄漏。
ThreadLocal.ThreadLocalMap使用數組存儲元素,與HashMap不同,它通過開放定址法解決hash沖突,不存在鏈表,通過動態擴容數組可無限存儲元素,數組元素的類型為Entry。
當我們往ThreadLocal.ThreadLocalMap寫入一個key-value時,ThreadLocalMap把key和value包裝成一個Entry,并通過key的hashcode值計算索引值,將Entry放到數組中。
ThreadLocal.ThreadLocalMap.Entry類的源碼如下:
static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
雖然key為弱引用的ThreadLocal,當ThreadLocal釋放時,Entry的key變為null,但由于value還在,如果Thread不釋放,那么Entry也就不會被垃圾收集器回收。
但如果線程是臨時創建的,在方法中創建且沒有被其它地方引用,當線程執行完成時就會被JVM銷毀,在線程實際退出之前由JVM調用線程的exit方法給線程對象完成清理。exit方法部分源碼如下。
private void exit() { ...... threadLocals = null; inheritableThreadLocals = null; ...... }
因此,只要Thread對象的exit方法被調用,就不會存在內存泄漏問題。只要線程用完就銷毀,那么使用InheritableThreadLocal,在子線程中不需要調用InheritableThreadLocal的remove方法也不會存在內存泄漏的可能。
比如我們在項目中使用InheritableThreadLocal實現將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; }
在此案例中,由于子線程只是臨時創建的,所以我們不需要在子線程中調用InheritableThreadLocal的remove方法,只需要在父線程調用一次remove方法,因為tomcat的work線程是不會在一次請求結束后就銷毀的。
現在我們已經知道了InheritableThreadLocal是如何實現將數據傳遞給子線程的,思考題的答案也就有了一半:由于InheritableThreadLocal只能將線程上下文傳遞給當前線程創建的子線程,所以只有線程池中的線程是由當前線程創建的才能夠傳遞。
但要知道另一半答案我們還需要從線程池中尋找。
使用不同參數構建的線程池不同,常見的有單線程的線程池、只有固定數量核心線程的線程池、有固定數量核心線程和非核心線程的線程池、只有非核心線程的線程池。
線程池的幾個構造參數說明如下:
corePoolSize:核心線程數,不會被釋放的線程數量(設置allowCoreThreadTimeOut為ture時例外);
maximumPoolSize:線程池的最大線程數,等于核心線程與非核心線程的數量總和;
keepAliveTime:非核心線程最大空閑等待時間,在指定空閑時間后如果還沒有任務則釋放該線程;
workQueue:任務隊列,當核心線程數用完時,任務被放入隊列。
一、線程池是臨時線程池
如果線程池是在當前線程創建的,且任務都是由當前線程提交的,線程池用完就消毀了,那么不管是哪種線程池,池中的線程都是由當前線程所創建,在這種場景下,InheritableThreadLocal能夠將Context傳給給線程池中的任一線程。
二、線程池是全局線程池
如果線程池是全局線程池:
沒有核心線程且非核心線程的keepAliveTime等于0:線程都是用到才創建,且由于keepAliveTime等于0,線程用完可能就釋放了,在這種場景下,相當于是由當前線程創建子線程執行任務,因此能夠實現透傳;
沒有非核心線程:前(核心線程數)個任務的提交都會創建線程,也都是由當前線程創建,所以只有這幾個任務的執行是能夠正常獲取父線程寫入InheritableThreadLocal的數據的,后面提交的任務就不知道會被哪個核心線程拉取執行了;
到此,相信大家對“java中InheritableThreadLocal異步傳遞數據的實現原理”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。