關(guān)于TransmittableThreadLocal線程池中線程復(fù)用問題的解決方案
TransmittableThreadLocal線程復(fù)用問題
TTL對線程或者線程池的核心通過裝飾器模式做了處理,核心如下:
- capture:獲取父線程中的值,包括引用對象或普通對象
- replay:回放:備份、將父線程的值設(shè)置到子線程
- restore:將子線程執(zhí)行之前backup的值設(shè)置回子線程的ThreadLocal
一、TTL在線程池場景
線程池復(fù)用線程,如果子線程執(zhí)行完未移除上下文,則會導(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)打開運行):
1-pool-1-thread-1:田曉霞
2-pool-1-thread-1:孫少平
分析:上述案例定義了只有一個工作線程執(zhí)行隊列中線程的線程池,這樣做是為了立馬復(fù)現(xiàn)線程復(fù)用的效果;第一個線程中第一了上下文值引用對象,第二個線程之中沒有定義,效果是第二個線程獲取到了第一個線程之中設(shè)置的上下文變量;如果將第一個線程中(2)注釋打開,則第二個線程獲取不到第一個線程中設(shè)置的上下文變量;
如果將(1)打開會是什么效果,效果是第二個線程拿不到第一個線程中設(shè)置的上下文值,這又是為什么呢?在第一個線程中又沒有主動remove掉為何使用了TTL修飾的線程池就拿不到符合預(yù)期了呢?
如果仔細分析源碼會發(fā)現(xiàn)TTL修飾的線程使用裝飾器模式,第一步capture捕獲主線程上下文,第二部replay回放、backup備份子線程上下文、將主線程上下文值設(shè)置到子線程,第三部restore恢復(fù)階段、將backup備份恢復(fù)到子線程上下文,因為子線程上下文執(zhí)行沒有任何值,restore恢復(fù)后上下文是干凈的,上下文沒有任何值;那在第二個線程復(fù)用線程的時候是取不到指的,符合預(yù)期;
如果將(1)(3)打開運行結(jié)果也是符合預(yù)期的,這里面有個問題,無論線程一、線程二執(zhí)行完成后線程池中復(fù)用的線程都會持有一個User對象的引用,這樣GC的時候就無法回收資源,如果線程池持有的引用對象足夠多,引用對象占用的內(nèi)存足夠大的時候就有可能引發(fā)OOM異常;
如果將代碼(4)打開則會移除主線程的上下文,但是子線程從父線程繼承的上下文屬性是無法移除的;
二、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)系,相當于TTL由InheritableThreadLocal回退到了ThreadLocal
TransmittableThreadLocal<String> t1 = new TransmittableThreadLocal<String>() { protected String childValue(String parentValue) { return initialValue(); } }
此方法是去除掉父子線程之間的繼承關(guān)系,會將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實現(xiàn)全過程
Spring Security基于Spring開發(fā),項目中如果使用Springboot作為基礎(chǔ),配合Spring Security做權(quán)限更加方便,而Shiro需要和Spring進行整合開發(fā)。因此作為spring全家桶中的Spring Security在java領(lǐng)域很常用2022-07-07SpringBoot整合Ip2region獲取IP地址和定位的詳細過程
ip2region v2.0 - 是一個離線IP地址定位庫和IP定位數(shù)據(jù)管理框架,10微秒級別的查詢效率,提供了眾多主流編程語言的 xdb 數(shù)據(jù)生成和查詢客戶端實現(xiàn) ,這篇文章主要介紹了SpringBoot整合Ip2region獲取IP地址和定位,需要的朋友可以參考下2023-06-06Java的靜態(tài)方法Arrays.asList()使用指南
Arrays.asList() 是一個 Java 的靜態(tài)方法,它可以把一個數(shù)組或者多個參數(shù)轉(zhuǎn)換成一個 List 集合,這個方法可以作為數(shù)組和集合之間的橋梁,方便我們使用集合的一些方法和特性,本文將介紹 Arrays.asList() 的語法、應(yīng)用場景、坑點和總結(jié)2023-09-09