Java 并发编程实战
王宝令
资深架构师
72485 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
学习攻略 (1讲)
Java 并发编程实战
15
15
1.0x
00:00/00:00
登录|注册

30 | 线程本地存储模式:没有共享,就没有伤害

InheritableThreadLocal
内存泄露问题
Java实现
SafeDateFormat示例
ThreadId示例
Java SDK的实现
避免共享的方案
与继承性
与内存泄露
工作原理
使用方法
线程本地存储
局部变量
课后思考
总结
ThreadLocal
避免共享
线程本地存储模式

该思维导图由 AI 生成,仅供参考

民国年间某山东省主席参加某大学校庆演讲,在篮球场看到十来个人穿着裤衩抢一个球,观之实在不雅,于是怒斥学校的总务处长贪污,并且发话:“多买几个球,一人发一个,省得你争我抢!”小时候听到这个段子只是觉得好玩,今天再来看,却别有一番滋味。为什么呢?因为其间蕴藏着解决并发问题的一个重要方法:避免共享
我们曾经一遍一遍又一遍地重复,多个线程同时读写同一共享变量存在并发问题。前面两篇文章我们突破的是写,没有写操作自然没有并发问题了。其实还可以突破共享变量,没有共享变量也不会有并发问题,正所谓是没有共享,就没有伤害
那如何避免共享呢?思路其实很简单,多个人争一个球总容易出矛盾,那就每个人发一个球。对应到并发编程领域,就是每个线程都拥有自己的变量,彼此之间不共享,也就没有并发问题了。
我们在《11 | Java 线程(下):为什么局部变量是线程安全的?》中提到过线程封闭,其本质上就是避免共享。你已经知道通过局部变量可以做到避免共享,那还有没有其他方法可以做到呢?有的,Java 语言提供的线程本地存储(ThreadLocal)就能够做到下面我们先看看 ThreadLocal 到底该如何使用。

ThreadLocal 的使用方法

确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

线程本地存储模式是解决并发问题的重要方法,其核心思想是避免共享变量,从而避免并发问题的发生。文章通过引人入胜的校庆篮球场的段子引出了避免共享的重要性,介绍了线程封闭和局部变量的方式来避免共享,并详细讲解了Java语言提供的线程本地存储(ThreadLocal)的使用方法和工作原理。通过示例代码和图示,阐述了ThreadLocal的内部实现机制,以及在线程池中使用ThreadLocal可能导致的内存泄露问题。此外,还介绍了InheritableThreadLocal的继承性特点,但不建议在线程池中使用。总体来说,本文深入浅出地介绍了线程本地存储模式的原理和应用,对于并发编程领域的读者具有很高的参考价值。 线程本地存储模式本质上是一种避免共享的方案,由于没有共享,所以自然也就没有并发问题。如果需要在并发场景中使用一个线程不安全的工具类,最简单的方案就是避免共享。避免共享有两种方案,一种是将工具类作为局部变量使用,另一种是线程本地存储模式。线程本地存储模式是解决并发问题的常用方案,所以Java SDK也提供了相应的实现:ThreadLocal。然而,在线程池中使用ThreadLocal仍可能导致内存泄漏,因此使用ThreadLocal需要谨慎。在实际工作中,许多平台型的技术方案都采用ThreadLocal来传递上下文信息,例如Spring使用ThreadLocal来传递事务信息。在异步场景中,是否可以使用Spring的事务管理器呢?这是一个值得思考的问题。 总的来说,本文深入探讨了线程本地存储模式的原理和应用,对于并发编程领域的读者具有很高的参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 并发编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(57)

  • 最新
  • 精选
  • 右耳听海
    有个疑问请教老师,避免共享变量的两种解决方案,在高并发情况下,使用局部变量会频繁创建对象,使用threadlocal也是针对线程创建新变量,都是针对线程维度,threadlocal并未体现出什么优势,为什么还要用threadlocal

    作者回复: threadlocal=线程数,局部变量=调用量,差距太大了

    2019-05-07
    12
    128
  • 晓杰
    不可以,因为ThreadLocal内的变量是线程级别的,而异步编程意味着线程不同,不同线程的变量不可以共享

    作者回复: 👍

    2019-05-07
    100
  • 承香墨影
    老师您好,有个问题想请教。 在线程池中使用 ThreadLocal,您给的解决方案是,使用后手动释放。 那这样和使用线程的局部变量有什么区别?每次线程执行的时候都去创建对象并存储在 ThreadLocal 中,用完就释放掉了,下次执行依然需要重新创建,并存入 ThreadLocalMap 中,这样并没有解决局部变量频繁创建对象的问题。

    作者回复: 这种用法一般是为了在一个线程里传递全局参数,也叫上下文信息,局部变量不能跨方法,这个用法不是用来解决局部变量重复创建的

    2019-05-22
    66
  • QQ怪
    上面有些同学说多线程是simpledateformat会打印出一样名称的对象,我刚刚也试了下,的确可以复现,但其实是simpledateformat对象的toString()方法搞得鬼,该类是继承object类的tostring方法,如下有个hashcode()方法,但该类重写了hashcode方法,在追溯到hashcode方法,pattern.hashcode(),pattern就是我们的yyyy-MM-dd,这个是一直保持不变的,现在终于真相大白了

    作者回复: 感谢回复!!!!

    2019-05-07
    2
    50
  • linqw
    自己写了下对ThreadLocal的源码分析https://juejin.im/post/5ce7e0596fb9a07ee742ba79,感兴趣的可以看下哦,老师也帮忙看下哦

    作者回复: 有心👍

    2019-05-25
    2
    15
  • So
    一个ThreadLocal只能保存一个变量,那如果有多个变量要保存,是不是要建多个ThreadLocal?

    作者回复: 是的

    2019-09-11
    3
    13
  • 晓杰
    请问一下老师,我刚刚对simpledateformat加threadlocal,但是不同线程得到的simpledateformat对象是一样的,代码如下: public class Tool { public static void main(String[] args) throws Exception{ System.out.println(SafeDateFormat.get()); System.out.println(Thread.currentThread().getName()); new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); System.out.println(SafeDateFormat.get()); } }).start(); } static class SafeDateFormat{ static final ThreadLocal<SimpleDateFormat> sdf = ThreadLocal.withInitial(()->new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); static SimpleDateFormat get(){ return sdf.get(); } } } 请问存在什么问题

    作者回复: 有同学已经找到原因了,是tostring的锅

    2019-05-07
    7
  • 天天向善
    有个疑问请教,线程多路复用,使用thread local有什么注意的,会不会不同请求获取内容相同,造成后续业务错误

    作者回复: 很有这种可能,所以不能用它存状态数据

    2019-05-08
    2
    5
  • 盐多必失
    某山东省主席…… 宝令小哥哥这加密算法做得太好了,^_^

    作者回复: 大智若愚,谁说的清呢,一起鄙视那些不加密的吧😄

    2019-06-09
    4
  • xinglichea
    老师, 文中提到解决内存泄露的方法是显示调用remove()方法,但貌似ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value,即:在ThreadLocalMap中的setEntry()、getEntry(),如果遇到key == null的情况,会对value设置为null。 那么是不是可以说明,如果线程在后续操作中会继续调用set()、get()的话,就不需要显示调用remove()了。

    作者回复: 工程上建议不要做假设,万一出了问题不好查

    2019-08-26
    2
    3
收起评论
显示
设置
留言
57
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部