日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
InheritableThreadLocal異步傳遞數(shù)據(jù)實現(xiàn)原理

由于上次主要分析如何解決異步獲取不到Session問題,所以沒有展開分析留下的那個思考題:使用InheritableThreadLocal傳遞Session,為什么說使用線程池不一定能獲取到Session,而不是一定獲取不到?

江孜網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián)公司,江孜網(wǎng)站設(shè)計制作,有大型網(wǎng)站制作公司豐富經(jīng)驗。已為江孜1000多家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)網(wǎng)站建設(shè)要多少錢,請找那個售后服務(wù)好的江孜做網(wǎng)站的公司定做!

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

Thread類有兩個ThreadLocal字段:

 
 
 
 
  1. public class Thread implements Runnable { 
  2.     ThreadLocal.ThreadLocalMap threadLocals = null; 
  3.     ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; 

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

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

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

  • 1、ThreadLocal或者InheritableThreadLocal先調(diào)用Thread#currentThread靜態(tài)方法獲取當前線程的Thread對象;
  • 2、獲取Thread對象的threadLocals或者inheritableThreadLocals;
  • 3、將ThreadLocal或者InheritableThreadLocal對象作為key,將數(shù)據(jù)寫入到當前Thread對象的threadLocals或者inheritableThreadLocals字段中。

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

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

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

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

 
 
 
 
  1. private void init(ThreadGroup g, Runnable target, String name, 
  2.                       long stackSize, AccessControlContext acc, 
  3.                       boolean inheritThreadLocals) { 
  4.         ...... 
  5.         if (inheritThreadLocals && parent.inheritableThreadLocals != null) 
  6.             this.inheritableThreadLocals = 
  7.                 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); 
  8.        ...... 

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

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

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

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

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

 
 
 
 
  1. static class Entry extends WeakReference> { 
  2.    Object value; 
  3.    Entry(ThreadLocal k, Object v) { 
  4.        super(k); 
  5.        value = v; 
  6.    } 

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

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

 
 
 
 
  1. private void exit() { 
  2.     ...... 
  3.     threadLocals = null; 
  4.     inheritableThreadLocals = null; 
  5.     ...... 

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

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

 
 
 
 
  1. @GetMapping("/test") 
  2. public SsoUser test() { 
  3.     // 獲取登錄用戶 
  4.     SsoUser ssoUser = SsoUserManager.curLoggedUser(); 
  5.     System.out.println(ssoUser.getUserCode()); 
  6.     // 支持子線程傳遞 
  7.     new Thread(() -> { 
  8.         try { 
  9.             Thread.sleep(100); 
  10.             SsoUser ssoUser2 = SsoUserManager.curLoggedUser(); 
  11.             System.out.println(ssoUser2.getUserCode()); 
  12.         } catch (InterruptedException e) { 
  13.         } 
  14.     }).start(); 
  15.     return ssoUser; 

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

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

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

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

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

  • corePoolSize:核心線程數(shù),不會被釋放的線程數(shù)量(設(shè)置allowCoreThreadTimeOut為ture時例外);
  • maximumPoolSize:線程池的最大線程數(shù),等于核心線程與非核心線程的數(shù)量總和;
  • keepAliveTime:非核心線程最大空閑等待時間,在指定空閑時間后如果還沒有任務(wù)則釋放該線程;
  • workQueue:任務(wù)隊列,當核心線程數(shù)用完時,任務(wù)被放入隊列。

一、線程池是臨時線程池

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

二、線程池是全局線程池

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

  • 沒有核心線程且非核心線程的keepAliveTime等于0:線程都是用到才創(chuàng)建,且由于keepAliveTime等于0,線程用完可能就釋放了,在這種場景下,相當于是由當前線程創(chuàng)建子線程執(zhí)行任務(wù),因此能夠?qū)崿F(xiàn)透傳;
  • 沒有非核心線程:前(核心線程數(shù))個任務(wù)的提交都會創(chuàng)建線程,也都是由當前線程創(chuàng)建,所以只有這幾個任務(wù)的執(zhí)行是能夠正常獲取父線程寫入InheritableThreadLocal的數(shù)據(jù)的,后面提交的任務(wù)就不知道會被哪個核心線程拉取執(zhí)行了;
  • 其它:....

因此,如果線程池是全局線程池,那么無論是哪個情況,都不建議使

本文轉(zhuǎn)載自微信公眾號「Java藝術(shù)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系Java藝術(shù)公眾號。


文章題目:InheritableThreadLocal異步傳遞數(shù)據(jù)實現(xiàn)原理
文章地址:http://www.5511xx.com/article/dhjdjip.html