手写系列-ThreadLocal/TransmittableThreadLocal

原理图


弱引用环境下,发生内存泄漏的原理





父子线程传递


get方法会去检查Entyr当中key==null的情况

TransmittableThreadLocal—父子线程间线程本地变量

问题:若是在线程中开启子线程,那么子线程也会存在一个ThreadLocalMap对象,但是它不存在父线程ThreadLocalMap对象中的值。

@Slf4jpublicclasstestThreadLocal{@TestpublicvoidtestThreadLocal2()throws InterruptedException{ThreadLocal<String>local=newThreadLocal<>();try{local.set("我是主线程");ExecutorServiceexecutorService=Executors.newFixedThreadPool(1);CountDownLatchc1=newCountDownLatch(1);CountDownLatchc2=newCountDownLatch(1);executorService.execute(()->{System.out.println("线程1"+local.get());c1.countDown();});c1.await();executorService.execute(()->{System.out.println("线程2"+local.get());c2.countDown();});c2.await();executorService.shutdownNow();}finally{//使用完毕,清除线程中ThreadLocalMap中的key。local.remove();}}}

可继承的InheritableThreadLocal

@Slf4jpublicclasstestThreadLocal{@TestpublicvoidtestInheritableThreadLocal()throws InterruptedException{ThreadLocal<String>local=newInheritableThreadLocal<>();local.set("我是主线程");newThread(()->{System.out.println("子线程1"+local.get());}).start();Thread.sleep(2000);}}

最终解决方案TransmittableThreadLocal

阿里巴巴开源解决方案:https://github.com/alibaba/transmittable-thread-local

alibaba提供了一系列的包装类,可以对ExecutorServic及其子类进行装饰,由TtlExecutors类实现。

@TestpublicvoidtestInheritableThreadLocal3()throws InterruptedException{TransmittableThreadLocal<String>local=newTransmittableThreadLocal<>();local.set("我是主线程");//生成额外的代理ExecutorServiceexecutorService=Executors.newFixedThreadPool(1);//核心装饰代码executorService=TtlExecutors.getTtlExecutorService(executorService);CountDownLatchc1=newCountDownLatch(1);CountDownLatchc2=newCountDownLatch(1);executorService.submit(()->{System.out.println("我是线程1:"+local.get());c1.countDown();});c1.await();local.set("修改主线程");System.out.println(local.get());executorService.submit(()->{System.out.println("我是线程2:"+local.get());c2.countDown();});c2.await();}

agent实现

需配置启动参数-javaagent:path/to/transmittable-thread-local-2.x.x.jar

@TestpublicvoidtestTTL()throws InterruptedException{TransmittableThreadLocal<String>local=newTransmittableThreadLocal<>();local.set("我是主线程");//生成额外的代理CountDownLatchc1=newCountDownLatch(1);CountDownLatchc2=newCountDownLatch(1);CompletableFuture.supplyAsync(()->{System.out.println("开启了线程1:"+local.get());c1.countDown();return"线程1的值";});c1.await();local.set("修改主线程");System.out.println(local.get());CompletableFuture.supplyAsync(()->{System.out.println("开启了线程2:"+local.get());c1.countDown();return"线程2的值";});c2.await();System.out.println("我是主线程:"+local.get());}


TransmittableThreadLocal源码分析:

TransmittableThreadLocal设计思路如下:总得来说,就是在将异步任务派发给线程池时,对其做一下上下文传递的处理。

第一步:主线程获取上下文,传递给任务暂存。

1 之后的操作都将是异步执行线程操作的。

第二步:异步执行线程将原有上下文取出,暂时保存。并将主线程传递过来的上下文设置。

第三步:执行异步任务

第四步:将原有上下文设置回去。

privatestaticThreadLocal<String>contextHolder=newThreadLocal<>();publicstatic<T>CompletableFuture<T>invokeToCompletableFuture(Supplier<T>supplier,String errorMessage){// 第一步String context=contextHolder.get();Supplier<T>newSupplier=()->{// 第二步String origin=contextHolder.get();try{contextHolder.set(context);// 第三步returnsupplier.get();}finally{// 第四步contextHolder.set(origin);log.info(origin);}};returnCompletableFuture.supplyAsync(newSupplier).exceptionally(e->{thrownewServerErrorException(errorMessage,e);});}// test codepublicstaticvoidmain(String[]args)throws ExecutionException,InterruptedException{contextHolder.set("main");log.info(contextHolder.get());CompletableFuture<String>context=invokeToCompletableFuture(()->test.contextHolder.get(),"error");log.info(context.get());}

TransmittableThreadLocal核心类分析:

其使用方法本质上与上述提到的 CallableWrapper 和 DelegatingExecutor 是一样的,并且为了方便使用,对外提供了静态工厂方法或工具类。

整体主要是三个部分:任务( TtlCallable )、线程池( ExecutorServiceTtlWrapper )、ThreadLocal( TransmittableThreadLocal )。其实对应上述讲到的 CallableWrapper、DelegatingExecutor、InheritableThreadLocal。

精简版本的transmittalbeThreadLocal

因为TTL是继承自ITL,所以在线程池中的线程被创建的时候,会自动继承,且会一直存在,个人感觉没有必要,下面是TTL的精简版代码,继承自TL,但实现原理一致。

publicclassHTransmittableThreadLocal<T>extendsThreadLocal<T>{/** * 保存需要具备传播性的 HTransmittableThreadLocal */privatestaticfinalThreadLocal<WeakHashMap<HTransmittableThreadLocal<Object>,?>>holder=ThreadLocal.withInitial(WeakHashMap::new);@OverridepublicfinalTget(){Tvalue=super.get();addThisToHolder();returnvalue;}@Overridepublicfinalvoidset(Tvalue){super.set(value);addThisToHolder();}@Overridepublicfinalvoidremove(){removeThisFromHolder();super.remove();}privatevoidsuperRemove(){super.remove();}@SuppressWarnings("unchecked")privatevoidaddThisToHolder(){if(!holder.get().containsKey(this)){holder.get().put((HTransmittableThreadLocal<Object>)this,null);}}privatevoidremoveThisFromHolder(){holder.get().remove(this);}/** * 留给子类实现以实现值传递,默认是引用传递。在 {@link #capture()} 被调用时使用 */protectedTcopyValue(TparentValue){returnparentValue;}privatestaticclassSnapshot{finalHashMap<HTransmittableThreadLocal<Object>,Object>hTtlValue;privateSnapshot(HashMap<HTransmittableThreadLocal<Object>,Object>hTtlValue){this.hTtlValue=hTtlValue;}}/** * 在创建子线程任务时调用,以捕获当前线程需要传播的值 */publicstaticObjectcapture(){HashMap<HTransmittableThreadLocal<Object>,Object>hTtlValue=newHashMap<>();for(HTransmittableThreadLocal<Object>threadLocal:holder.get().keySet()){hTtlValue.put(threadLocal,threadLocal.copyValue(threadLocal.get()));}returnnewSnapshot(hTtlValue);}/** * 在子线程的任务执行前进行调用,将父线程捕获的需要传播的值在子线程进行回放,同时返回子线程中需要传播的值 */publicstaticObjectreplay(Objectcaptured){finalSnapshotcapturedSnapshot=(Snapshot)captured;returnnewSnapshot(replayTtlValues(capturedSnapshot.hTtlValue));}privatestaticHashMap<HTransmittableThreadLocal<Object>,Object>replayTtlValues(HashMap<HTransmittableThreadLocal<Object>,Object>captured){HashMap<HTransmittableThreadLocal<Object>,Object>backup=newHashMap<>();for(finalIterator<HTransmittableThreadLocal<Object>>iterator=holder.get().keySet().iterator();iterator.hasNext();){HTransmittableThreadLocal<Object>threadLocal=iterator.next();// 备份子线程的TTLbackup.put(threadLocal,threadLocal.get());// 如果holder中的TTL不在父线程的TTL中,则进行holder的清理if(!captured.containsKey(threadLocal)){iterator.remove();threadLocal.superRemove();}}setTtlValuesTo(captured);returnbackup;}/** * 在子线程的任务执行后进行调用,将子线程需要传播的值进行恢复 */publicstaticvoidrestore(Objectbackup){finalSnapshotbackupSnapshot=(Snapshot)backup;restoreTtlValues(backupSnapshot.hTtlValue);}privatestaticvoidrestoreTtlValues(HashMap<HTransmittableThreadLocal<Object>,Object>backup){for(finalIterator<HTransmittableThreadLocal<Object>>iterator=holder.get().keySet().iterator();iterator.hasNext();){HTransmittableThreadLocal<Object>threadLocal=iterator.next();// 如果holder中的TTL不在之前子线程备份的TTL中,则进行holder的清理if(!backup.containsKey(threadLocal)){iterator.remove();threadLocal.superRemove();}}setTtlValuesTo(backup);}/** * 为TTL赋值 */privatestaticvoidsetTtlValuesTo(HashMap<HTransmittableThreadLocal<Object>,Object>hTtlValues){for(Map.Entry<HTransmittableThreadLocal<Object>,Object>entry:hTtlValues.entrySet()){finalHTransmittableThreadLocal<Object>threadLocal=entry.getKey();threadLocal.set(entry.getValue());}}}publicclassHTtlRunnableimplementsRunnable{privatefinalAtomicReference<Object>capturedRef;privatefinalRunnablerunnable;publicHTtlRunnable(Runnablerunnable){this.capturedRef=newAtomicReference<Object>(capture());this.runnable=runnable;}@Overridepublicvoidrun(){finalObjectcaptured=capturedRef.get();finalObjectbackup=replay(captured);try{runnable.run();}finally{restore(backup);}}}

publicclassHTtlTest{publicstaticfinalHTransmittableThreadLocal<String>hTtl=newHTransmittableThreadLocal<>();publicstaticvoidmain(String[]args)throwsException{ExecutorServicethreadPool=Executors.newSingleThreadExecutor();// 模拟第一次开启子任务hTtl.set("hTtl-value-1");printThreadIdAndContext();threadPool.submit(newHTtlRunnable(HTtlTest::printThreadIdAndContext)).get();printThreadIdAndContext();System.out.println("----------------------");// 模拟第二次开启子任务hTtl.set("hTtl-value-2");printThreadIdAndContext();threadPool.submit(newHTtlRunnable(HTtlTest::printThreadIdAndContext)).get();printThreadIdAndContext();}privatestaticvoidprintThreadIdAndContext(){System.out.printf("【%2d】:%s%n",Thread.currentThread().getId(),hTtl.get());}}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,884评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,212评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,351评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,412评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,438评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,127评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,714评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,636评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,173评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,264评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,402评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,073评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,763评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,253评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,382评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,749评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,403评论 2 358

推荐阅读更多精彩内容