文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

InheritableThreadLocal异步传递数据实现原理

2024-12-03 10:57

关注

由于上次主要分析如何解决异步获取不到Session问题,所以没有展开分析留下的那个思考题:使用InheritableThreadLocal传递Session,为什么说使用线程池不一定能获取到Session,而不是一定获取不到?

在Java中,一个Java线程就是一个操作系统线程,创建一个线程需要通过new Thread创建,由JVM为Thread绑定操作系统线程,即便是使用线程池,也需要通过new Thread创建线程。

Thread类有两个ThreadLocal字段:

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

InheritableThreadLocal是ThreadLocal的子类,本质上就是一个ThreadLocal。

在Thread类中,threadLocals与inheritableThreadLocals都是线程对象私有的,只能通过当前线程对象写入和获取数据,只是Thread会将写入inheritableThreadLocals的数据传递给子线程的inheritableThreadLocals。

当我们往ThreadLocal或者InheritableThreadLocal写入数据时,写入过程为:

因此,Thread的threadLocals与inheritableThreadLocals的key是ThreadLocal或者InheritableThreadLocal实例,value是写入的数据。

关于threadLocals我在前面一篇《反向理解ThreadLocal,或许这样更容易理解》已经详细介绍过了,本篇重点分析inheritableThreadLocals是如何传递给子线程的。

默认情况下,当我们使用new Thread()创建一个线程时,在Thread的构造方法中会通过Thread#currentThread获取当前线程,将当前线程作为新创建线程的父线程,所以就有了父子线程关系。

无论使用哪个重载的构造方法创建Thread,都会在构造方法中调用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参数默认为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类的源码如下:

  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变为null,但由于value还在,如果Thread不释放,那么Entry也就不会被垃圾收集器回收。

但如果线程是临时创建的,在方法中创建且没有被其它地方引用,当线程执行完成时就会被JVM销毁,在线程实际退出之前由JVM调用线程的exit方法给线程对象完成清理。exit方法部分源码如下。

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

因此,只要Thread对象的exit方法被调用,就不会存在内存泄漏问题。只要线程用完就销毁,那么使用InheritableThreadLocal,在子线程中不需要调用InheritableThreadLocal的remove方法也不会存在内存泄漏的可能。

比如我们在项目中使用InheritableThreadLocal实现将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; 

在此案例中,由于子线程只是临时创建的,所以我们不需要在子线程中调用InheritableThreadLocal的remove方法,只需要在父线程调用一次remove方法,因为tomcat的work线程是不会在一次请求结束后就销毁的。

现在我们已经知道了InheritableThreadLocal是如何实现将数据传递给子线程的,思考题的答案也就有了一半:由于InheritableThreadLocal只能将线程上下文传递给当前线程创建的子线程,所以只有线程池中的线程是由当前线程创建的才能够传递。

但要知道另一半答案我们还需要从线程池中寻找。

使用不同参数构建的线程池不同,常见的有单线程的线程池、只有固定数量核心线程的线程池、有固定数量核心线程和非核心线程的线程池、只有非核心线程的线程池。

线程池的几个构造参数说明如下:

一、线程池是临时线程池

如果线程池是在当前线程创建的,且任务都是由当前线程提交的,线程池用完就消毁了,那么不管是哪种线程池,池中的线程都是由当前线程所创建,在这种场景下,InheritableThreadLocal能够将Context传给给线程池中的任一线程。

二、线程池是全局线程池

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

因此,如果线程池是全局线程池,那么无论是哪个情况,都不建议使

本文转载自微信公众号「Java艺术」,可以通过以下二维码关注。转载本文请联系Java艺术公众号。

 

来源: Java艺术内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯