關(guān)于TransmittableThreadLocal線程池中線程復(fù)用問題的解決方案
TransmittableThreadLocal線程復(fù)用問題
TTL對線程或者線程池的核心通過裝飾器模式做了處理,核心如下:
- capture:獲取父線程中的值,包括引用對象或普通對象
- replay:回放:備份、將父線程的值設(shè)置到子線程
- restore:將子線程執(zhí)行之前backup的值設(shè)置回子線程的ThreadLocal
一、TTL在線程池場景
線程池復(fù)用線程,如果子線程執(zhí)行完未移除上下文,則會(huì)導(dǎo)致后續(xù)線程可以取到之前線程設(shè)置的屬性
public class TtlErrorTest { private static final TransmittableThreadLocal<User> context = new TransmittableThreadLocal<>(); private static final ExecutorService service = Executors.newSingleThreadExecutor(); //TTL修飾過的線程池(1) // private static final Executor service = TtlExecutors.getTtlExecutor(Executors.newSingleThreadExecutor()); public static void main(String[] args) { //主線程設(shè)置上下文變量(3) //User user1 = new User(); //user1.setUsername("孫少平"); //user1.setPassword("123456"); //context.set(user1); Runnable runnable = new Runnable() { @Override public void run() { User user = new User(); user.setUsername("田曉霞"); context.set(user); System.out.println("1-" + Thread.currentThread().getName() + ":" + context.get()); //移除上下文變量(2) //context.remove(); } }; Runnable runnable1 = new Runnable() { @Override public void run() { System.out.println("2-" + Thread.currentThread().getName() + ":" + context.get()); } }; service.execute(runnable); service.execute(runnable1); //移除上下文(4) //ntext.remove(); } }
結(jié)果一:
1-pool-1-thread-1:田曉霞
2-pool-1-thread-1:田曉霞
結(jié)果二(將代碼中的(1)(3)打開運(yùn)行):
1-pool-1-thread-1:田曉霞
2-pool-1-thread-1:孫少平
分析:上述案例定義了只有一個(gè)工作線程執(zhí)行隊(duì)列中線程的線程池,這樣做是為了立馬復(fù)現(xiàn)線程復(fù)用的效果;第一個(gè)線程中第一了上下文值引用對象,第二個(gè)線程之中沒有定義,效果是第二個(gè)線程獲取到了第一個(gè)線程之中設(shè)置的上下文變量;如果將第一個(gè)線程中(2)注釋打開,則第二個(gè)線程獲取不到第一個(gè)線程中設(shè)置的上下文變量;
如果將(1)打開會(huì)是什么效果,效果是第二個(gè)線程拿不到第一個(gè)線程中設(shè)置的上下文值,這又是為什么呢?在第一個(gè)線程中又沒有主動(dòng)remove掉為何使用了TTL修飾的線程池就拿不到符合預(yù)期了呢?
如果仔細(xì)分析源碼會(huì)發(fā)現(xiàn)TTL修飾的線程使用裝飾器模式,第一步capture捕獲主線程上下文,第二部replay回放、backup備份子線程上下文、將主線程上下文值設(shè)置到子線程,第三部restore恢復(fù)階段、將backup備份恢復(fù)到子線程上下文,因?yàn)樽泳€程上下文執(zhí)行沒有任何值,restore恢復(fù)后上下文是干凈的,上下文沒有任何值;那在第二個(gè)線程復(fù)用線程的時(shí)候是取不到指的,符合預(yù)期;
如果將(1)(3)打開運(yùn)行結(jié)果也是符合預(yù)期的,這里面有個(gè)問題,無論線程一、線程二執(zhí)行完成后線程池中復(fù)用的線程都會(huì)持有一個(gè)User對象的引用,這樣GC的時(shí)候就無法回收資源,如果線程池持有的引用對象足夠多,引用對象占用的內(nèi)存足夠大的時(shí)候就有可能引發(fā)OOM異常;
如果將代碼(4)打開則會(huì)移除主線程的上下文,但是子線程從父線程繼承的上下文屬性是無法移除的;
二、TTL如何保證線程池中復(fù)用的線程不持有其它線程的屬性值
針對上述線程池復(fù)用可能導(dǎo)致內(nèi)存OOM的問題提供了兩種解決方案
方案一:通過TtlExecutors.getDefaultDisableInheritableThreadFactory()在線程池中創(chuàng)建禁止繼承父線程上下文的線程
public class TtlFactoryTest { private static final TransmittableThreadLocal<User> context = new TransmittableThreadLocal<>(); static final ExecutorService service = Executors.newFixedThreadPool(2, TtlExecutors.getDefaultDisableInheritableThreadFactory()); public static void main(String[] args) { User user = new User(); user.setUsername("田曉霞"); context.set(user); System.out.println("1-" + Thread.currentThread().getName() + "-" + context.get()); service.submit(new Runnable() { @Override public void run() { User user1 = new User(); user1.setUsername("田二"); context.set(user1); System.out.println("2-" + Thread.currentThread().getName() + "-" + context.get()); } }); System.out.println("3-" + Thread.currentThread().getName() + "-" + context.get()); } }
方案二:去除掉父子線程的繼承關(guān)系,相當(dāng)于TTL由InheritableThreadLocal回退到了ThreadLocal
TransmittableThreadLocal<String> t1 = new TransmittableThreadLocal<String>() { protected String childValue(String parentValue) { return initialValue(); } }
此方法是去除掉父子線程之間的繼承關(guān)系,會(huì)將TTL回退到ThreadLocal,對于非線程池的父子線程繼承關(guān)系是不適用的;
到此這篇關(guān)于關(guān)于TransmittableThreadLocal線程池中線程復(fù)用問題的解決方案的文章就介紹到這了,更多相關(guān)TransmittableThreadLocal線程復(fù)用問題內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot利用Lombok減少Java中樣板代碼的方法示例
spring Boot是非常高效的開發(fā)框架,lombok是一套代碼模板解決方案,將極大提升開發(fā)的效率,下面這篇文章主要給大家介紹了關(guān)于Spring Boot利用Lombok減少Java中樣板代碼的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-09-09Springboot詳解整合SpringSecurity實(shí)現(xiàn)全過程
Spring Security基于Spring開發(fā),項(xiàng)目中如果使用Springboot作為基礎(chǔ),配合Spring Security做權(quán)限更加方便,而Shiro需要和Spring進(jìn)行整合開發(fā)。因此作為spring全家桶中的Spring Security在java領(lǐng)域很常用2022-07-07SpringBoot整合Ip2region獲取IP地址和定位的詳細(xì)過程
ip2region v2.0 - 是一個(gè)離線IP地址定位庫和IP定位數(shù)據(jù)管理框架,10微秒級別的查詢效率,提供了眾多主流編程語言的 xdb 數(shù)據(jù)生成和查詢客戶端實(shí)現(xiàn) ,這篇文章主要介紹了SpringBoot整合Ip2region獲取IP地址和定位,需要的朋友可以參考下2023-06-06Java的靜態(tài)方法Arrays.asList()使用指南
Arrays.asList() 是一個(gè) Java 的靜態(tài)方法,它可以把一個(gè)數(shù)組或者多個(gè)參數(shù)轉(zhuǎn)換成一個(gè) List 集合,這個(gè)方法可以作為數(shù)組和集合之間的橋梁,方便我們使用集合的一些方法和特性,本文將介紹 Arrays.asList() 的語法、應(yīng)用場景、坑點(diǎn)和總結(jié)2023-09-09Java每7天日志自動(dòng)清理的項(xiàng)目實(shí)踐
在實(shí)際項(xiàng)目中由于服務(wù)器內(nèi)存有限,人工清理常會(huì)忘記,本文主要介紹了Java每7天日志自動(dòng)清理的項(xiàng)目實(shí)踐,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01java 文件目錄讀寫刪除操作詳細(xì)實(shí)現(xiàn)代碼
這篇文章主要介紹了java 文件讀寫刪操作詳細(xì)實(shí)現(xiàn)代碼,需要的朋友可以參考下2017-09-09詳解java倒計(jì)時(shí)三種簡單實(shí)現(xiàn)方式
這篇文章主要介紹了詳解java倒計(jì)時(shí)三種簡單實(shí)現(xiàn)方式,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09