前言

记得我几年前第一次面试的时候,就是被问了这个,记得面试官直接就让我说说ThreadLocal的实现原理以及平时有没有见过哪些地方用到了。
我当时初入职场,还是一个大菜鸟,所以直接就被干蒙了,至今还记忆犹新。

闲来无事,总结一下这块,其实仔细想想这个ThreadLocal,整体思路其实挺清晰的,但有些细节会有难度,可能会涉及到一些比较深的平时不用的知识,说实话我也还没有完全理清楚,但一直都在努力中。

概念

定义

我们说的ThreadLocal是java.lang包下的一个类,这个类提供特殊的线程局部变量,使得每个访问该变量的线程在其内部都有一个独立的初始化变量副本。
用人话解释:
先说普通类中定义的变量,我们都知道是多个线程共有的。
而ThreadLocal这个类中有个特殊的变量,特殊就特殊在针对不同的线程,在用这个ThreadLocal的时候,都能拿到本线程独有的值,你可以set,可以get,线程之间互不影响。

其实ThreadLocal这个概念,并不是java语言独有的,其实很多语言都有这个概念,只不过java中是用哈希表实现了这个概念。

特点

简单,开销小,线程安全。

哪里用ThreadLocal

1、Quartz的SimpleSemaphore,提供资源隔离



看上图:
SimpleSemaphore里面就有个方法:obtrainLock方法,用synchronized锁
这个方法中有个很重的while操作(消费者处理完所有事情,需要等待新的事情,这个等待是一个while循环)
lockName是这个方法的入参,这个while方法的判断逻辑是如果locks这个HashSet中有这个lockName,这个线程就执行wait()方法,由于obtrainLock本身是一个所方法,然后再去执行wait(),你的线程就被完全阻塞在这里排队了。
试想,如果没有ThreadLocal先过滤,那么同一个线程的多次调用这个obtrainLock方法,带着相同的lockName,就会多次进入这个while循环,其实同一个线程是不需要多次进入这个操作的
所以通过在这个加锁操作之前用ThreadLocal判断(isLockOwner方法),将同一个线程带着相同lockName调用这个方法的次数,就减少到一次了,即只会第一次进入while循环,其他的都被isLockOwner方法挡住了
最终使得访问后面很重的操作的频率大大降低,算是一个优化。

2、Mybatis的SqlSessionManager,资源持有

我们知道Mybatis连数据库后,会有个连接池,里面会维护有多个连接,每次操作数据库,都需要拿到连接,再去操作,拿连接就是那个sqlSession.getConnection方法,每次操作都可能拿到任何一个连接。
如果想要支持事务,那必须让一次事务的所有操作,都必须让同一个连接处理,这样才能要么一起成功,要么一起失败,而一次事务的每个操作都需要从线程池中拿连接,那如何保证一次事务的每次操作拿到的都是同一个连接呢?
一次事务的多个操作一般都是一个线程去执行的,那其实问题就变成如何保证一个线程拿到的总是相同的一个连接,这里就用到了ThreadLocal,将当前线程拿到的连接保存在ThreadLocal中,下次该线程拿连接,就直接从ThreadLocal中拿这个连接,这样就保证了同一个线程永远拿到同一个连接,而其他线程拿哪个连接不受这个线程的影响。

我们看看具体的代码实现:
先是定义ThreadLocal,存放的就是SqlSession,每一个连接对应一个SqlSession

然后开始将一个线程的SqlSession放入ThreadLocal中

真正用的时候,比如commit,rollback等方法,就都从ThreadLocal中获取连接了。

3、Spring的TransactionContextHolder


TransactionContext也叫分布式事务资源池,保存的是当前环境的上下文,里面有个PlatformTransactionManager,这个就是执行commit和rollback的类,所以在分布式事务中也要保住同一个线程用同一个PlatformTransactionManager去执行commit或rollback,所以最终TransactionContext用ThreadLocal保存起来,达到效果。

4、登录

登录的时候,可以把每个线程的登录信息放在ThreadLocal中,就保证了同一个人的操作始终在同一个线程中。

ThreadLocal核心源码解读

1、首先,每个Thread中,都有一个成员变量threadLocals

这个是专门为ThreadLocal加的,具体threadLocals的赋值过程,是在ThreadLocal中
threadLocals的类型是ThreadLocal.ThreadLocalMap,这个ThreadLocalMap是ThreadLocal中的自定义的一个内部map类,key是ThreadLocal对象,value是每个线程的那个独有的变量副本。

2、ThreadLocal的get方法


先拿到当前线程
getMap方法,就是从当前线程中拿ThreadLocalMap,这个就是Thread中那个成员变量。
ThreadLocalMap的key是当前这个ThreadLocal对象,value就是我们这个get方法真正要返回的值。
如果能拿到ThreadLocalMap,那么就返回ThreadLocalMap中当前ThreadLocal对象对应的value值。
如果拿不到ThreadLocalMap,就去初始化value,最后再返回value。

总结

我们看到ThreadLocal的实现,就能清楚的知道为什么ThreadLocal可以保存不同线程的不同值了。
是因为其实最终这些值还是保存在了各个线程中的一个map中,而ThreadLocal仅仅是作为这个map的一个key。
那么对于一个线程,如果他遇到多个ThreadLocal,其实线程中的那个map就有多对值了。
有没有一种反向操作的感觉,乍一看以为这些值都是保存在ThreadLocal中的,最终发现还是在线程中保存。

注意

要注意的是,每个线程中的ThreadLocalMap是ThreadLocal中定义的一个静态类,相当于ThreadLocal重写了一个map,那有人会问了,为什么不直接用HashMap呢?

其实这是一个涉及到java垃圾回收的问题,重写的这个ThreadLocalMap,主要就是为了这个事情搞的。

我们知道其实HashMap中真正的数据是在一个个Entry中的,其实ThreadLocalMap也是这样,只不过ThreadLocalMap中的Entry是继承了WeakReference这个类。我们知道ThreadLocalMap中的key值,其实是ThreadLocal对象,在set某个对象的时候,需要根据这个对象的hash值去hash表中找槽,如果找到对应的槽后,槽上原来的对象被回收了,那对于的hash表上的位置的值就是null,那么ThreadLocalMap就会对这种已经废弃掉的null值对应的槽做一些处理(主要是重新回收这些槽,并重新分配hash表大小等)。这样相当于同步了垃圾回收的结果。

这就是为什么要重写hashMap了,因为hashMap不会处理这些逻辑,不处理就会造成槽不断的被已经回收的ThreadLocal的空对象占用着释放不出来,最后影响hash的查找,因为时间久了,每次正常hash后应该放的槽都被null占了,只能继续向后移着放。

ThreadLocal不仅要应付面试,更要真的理解,真的会用相关推荐

  1. Spring的七种事务传播行为,通俗易懂,绝不笼统,可以背诵,应付面试

    文章目录 什么是事务传播行为 PROPAGATION(propagation)_REQUIRED(required) PROPAGATION_NESTED(nested) PROPAGATION_RE ...

  2. 你真的理解反向传播吗?面试必备

    王小新 编译自 Medium 量子位 出品 | 公众号 QbitAI 当前,训练机器学习模型的唯一方式是反向传播算法. 深度学习框架越来越容易上手,训练一个模型也只需简单几行代码.但是,在机器学习面试 ...

  3. [转载] Java内存管理-你真的理解Java中的数据类型吗(十)

    参考链接: Java中的字符串类String 1 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 推荐阅读 第一季 0.Java的线程安全.单例模式.JVM内存结构等知识 ...

  4. 如何有效学习《恋上数据结构与算法》,更快地理解数据代码?

    1.关于数据结构与算法? 数据结构就是为算法服务的,算法要作用在特定的数据结构之上.数据结构和算法相辅相成. 广义上讲就是 "操作一组数据的方法",像是你有很多个视频,我们怎么才能 ...

  5. 你真的理解“吃亏是福”么?

    你真的理解"吃亏是福"么?且看 一个10几年的运维老鸟老男孩的随笔! 供朋友参考! 一定不要计较一时的得失! 有一次老男孩老师看电视,一位老大爷(当时70岁)感慨的说," ...

  6. 豆瓣最高评分8.1!万维钢:读懂这本书,你会比身边人更深的理解这个时代

    ▲ 数据汪特别推荐 点击上图进入玩酷屋 小木用真金白银来给大家送礼物啦,特别感谢这些年一直以来大家对我们的支持,才让我们越做越好.(点我参与送礼活动) 这几年全球各大科技巨头纷纷进入人工智能领域,催生 ...

  7. TCP 三次握手原理,你真的理解吗

    转载自  TCP 三次握手原理,你真的理解吗 最近,阿里中间件小哥哥蛰剑碰到一个问题--client端连接服务器总是抛异常.在反复定位分析.并查阅各种资料文章搞懂后,他发现没有文章把这两个队列以及怎么 ...

  8. 简单人物画像_你真的理解用户画像吗?| 船说

    " 「设计师沙龙」是ARK下半年开始逐渐形成的传统,由ARKers自发组织,分为视觉和交互两类,每月各举办一次.大家围绕一个话题展开,聊聊行业最新案例和工作上的心得,帮助大家共同进步. AR ...

  9. 【论文翻译】统一知识图谱学习和建议:更好地理解用户偏好

    一.摘要 将知识图谱(KG)纳入推荐系统有望提高推荐的准确性和可解释性.然而,现有方法主要假设KG是完整的并且简单地在实体原始数据或嵌入的浅层中转移KG中的"知识".这可能导致性能 ...

最新文章

  1. JavaScript库开发者们的规则
  2. 盖茨依然坐镇微软搜索团队 要与谷歌争高下
  3. Logic Pro for Mac(音频制作软件)V10.6.2中文版 支持M1芯片版mac
  4. PMP-PMBOK(第六版)--49个过程ITTO记忆口诀(第一辑)
  5. hive窗口函数总结
  6. pandas入门(3)
  7. 鸿蒙系统8月9日登场,华为自研鸿蒙系统将于8月9日正式登场,还有全新的鸿鹄芯片...
  8. 基于RNN实现搜狐新闻数据文本分类
  9. CCLE:肿瘤细胞系百科全书
  10. matlab对信号的滤波方法
  11. TextView设置MaxLength
  12. GOPATH 与工作空间
  13. 深聊性能测试,从入门到放弃之: Windows系统性能监控(三)任务管理器介绍及使用。
  14. 产品3C认证在哪里办理
  15. Gitblit服务器搭建及Git使用
  16. html.tex 下拉框,winform ComboBox 下拉框 显示图片效果 附完整源码
  17. 九大数据可视化利器,你有足球竞猜平台源码下载在使用吗?
  18. 男人女人应该不要随便离婚
  19. Python制作英文学习词典
  20. S2B2C社交电商系统怎么开发?

热门文章

  1. 05.MyBtais两种取值符号以及输入参数和输出参数
  2. protocol buffer相关
  3. String, StringBuffer, StringBuilder之间的区别
  4. accp8.0转换教材第1章多线程理解与练习
  5. node.js JS对象和JSON字符串之间的转换
  6. bzoj1085骑士精神(搜索)
  7. object-c 代理反向传值
  8. JAVA并发编程3_线程同步之synchronized关键字
  9. python-迭代器和生成器
  10. 库存商品表html源码,JSP+Servlet+数据库的方式完成一个简易的库存商品管理系统...