用法:
ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

  • ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
  • ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
  • ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
  • ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。
  1. public class MyThreadLocal
  2. {
  3. private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){
  4. /**
  5. * ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
  6. */
  7. @Override
  8. protected Object initialValue()
  9. {
  10. System.out.println("调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!");
  11. return null;
  12. }
  13. };
  14.  
  15. public static void main(String[] args)
  16. {
  17. new Thread(new MyIntegerTask("IntegerTask1")).start();
  18. new Thread(new MyStringTask("StringTask1")).start();
  19. new Thread(new MyIntegerTask("IntegerTask2")).start();
  20. new Thread(new MyStringTask("StringTask2")).start();
  21. }
  22.  
  23. public static class MyIntegerTask implements Runnable
  24. {
  25. private String name;
  26.  
  27. MyIntegerTask(String name)
  28. {
  29. this.name = name;
  30. }
  31.  
  32. @Override
  33. public void run()
  34. {
  35. for(int i = 0; i < 5; i++)
  36. {
  37. // ThreadLocal.get方法获取线程变量
  38. if(null == MyThreadLocal.threadLocal.get())
  39. {
  40. // ThreadLocal.et方法设置线程变量
  41. MyThreadLocal.threadLocal.set(0);
  42. System.out.println("线程" + name + ": 0");
  43. }
  44. else
  45. {
  46. int num = (Integer)MyThreadLocal.threadLocal.get();
  47. MyThreadLocal.threadLocal.set(num + 1);
  48. System.out.println("线程" + name + ": " + MyThreadLocal.threadLocal.get());
  49. if(i == 3)
  50. {
  51. MyThreadLocal.threadLocal.remove();
  52. }
  53. }
  54. try
  55. {
  56. Thread.sleep(1000);
  57. }
  58. catch (InterruptedException e)
  59. {
  60. e.printStackTrace();
  61. }
  62. }
  63. }
  64.  
  65. }
  66.  
  67. public static class MyStringTask implements Runnable
  68. {
  69. private String name;
  70.  
  71. MyStringTask(String name)
  72. {
  73. this.name = name;
  74. }
  75.  
  76. @Override
  77. public void run()
  78. {
  79. for(int i = 0; i < 5; i++)
  80. {
  81. if(null == MyThreadLocal.threadLocal.get())
  82. {
  83. MyThreadLocal.threadLocal.set("a");
  84. System.out.println("线程" + name + ": a");
  85. }
  86. else
  87. {
  88. String str = (String)MyThreadLocal.threadLocal.get();
  89. MyThreadLocal.threadLocal.set(str + "a");
  90. System.out.println("线程" + name + ": " + MyThreadLocal.threadLocal.get());
  91. }
  92. try
  93. {
  94. Thread.sleep(800);
  95. }
  96. catch (InterruptedException e)
  97. {
  98. e.printStackTrace();
  99. }
  100. }
  101. }
  102. }
  103. }

  结果:

ThreadLocal的核心机制:

  • 每个Thread线程内部都有一个Map。
  • Map里面存储线程本地对象(key)和线程的变量副本(value)
  • Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。

所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。

ThreadLocalMap
ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也独立实现。在ThreadLocalMap中,也是用Entry来保存K-V结构数据的。但是Entry中key只能是ThreadLocal对象,这点被Entry的构造方法已经限定死了。

  1. static class Entry extends WeakReference<ThreadLocal> {
  2. /** The value associated with this ThreadLocal. */
  3. Object value;
  4.  
  5. Entry(ThreadLocal k, Object v) {
  6. super(k);
  7. value = v;
  8. }
  9. } 

Entry继承自WeakReference(弱引用,生命周期只能存活到下次GC前),但只有Key是弱引用类型的,Value并非弱引用。

Hash冲突怎么解决?
和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有next引用,也就是说ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。

ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。

  1. /**
  2. * Increment i modulo len.
  3. */
  4. private static int nextIndex(int i, int len) {
  5. return ((i + 1 < len) ? i + 1 : 0);
  6. }
  7.  
  8. /**
  9. * Decrement i modulo len.
  10. */
  11. private static int prevIndex(int i, int len) {
  12. return ((i - 1 >= 0) ? i - 1 : len - 1);
  13. }

显然ThreadLocalMap采用线性探测的方式解决Hash冲突的效率很低,如果有大量不同的ThreadLocal对象放入map中时发送冲突,或者发生二次冲突,则效率很低。

所以这里引出的良好建议是:每个线程只存一个变量,这样的话所有的线程存放到map中的Key都是相同的ThreadLocal,如果一个线程要保存多个变量,就需要创建多个ThreadLocal,多个ThreadLocal放入Map中时会极大的增加Hash冲突的可能。

ThreadLocalMap的问题
由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

如何避免泄漏?
既然Key是弱引用,那么我们要做的事,就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。
如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。

总结

  1. 每个ThreadLocal只能保存一个变量副本,如果想要一个线程能够保存多个副本,就需要创建多个ThreadLocal。
  2. ThreadLocal内部的ThreadLocalMap键为弱引用,会有内存泄漏的风险。
  3. 适用于无状态,副本变量独立后不影响业务逻辑的高并发场景。如果如果业务逻辑强依赖于副本变量,则不适合用ThreadLocal解决,需要另寻解决方案。

Java多线程_ThreadLocal的更多相关文章

  1. 40个Java多线程问题总结

    前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多.越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的.这篇文章主要是对多线程的问题进行 ...

  2. Java多线程基础知识篇

    这篇是Java多线程基本用法的一个总结. 本篇文章会从一下几个方面来说明Java多线程的基本用法: 如何使用多线程 如何得到多线程的一些信息 如何停止线程 如何暂停线程 线程的一些其他用法 所有的代码 ...

  3. Java多线程系列--“JUC锁”03之 公平锁(一)

    概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...

  4. Java多线程系列--“JUC锁”04之 公平锁(二)

    概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...

  5. Java多线程--让主线程等待子线程执行完毕

    使用Java多线程编程时经常遇到主线程需要等待子线程执行完成以后才能继续执行,那么接下来介绍一种简单的方式使主线程等待. java.util.concurrent.CountDownLatch 使用c ...

  6. Java多线程 2 线程的生命周期和状态控制

    一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就 ...

  7. java 多线程 1 线程 进程

    Java多线程(一).多线程的基本概念和使用 2012-09-10 16:06 5108人阅读 评论(0) 收藏 举报  分类: javaSE综合知识点(14)  版权声明:本文为博主原创文章,未经博 ...

  8. 一起阅读《Java多线程编程核心技术》

    目录 第一章 Java多线程技能 (待续...)

  9. 第一章 Java多线程技能

    1.初步了解"进程"."线程"."多线程" 说到多线程,大多都会联系到"进程"和"线程".那么这两者 ...

随机推荐

  1. nonlocal 访问变量

    def counter(start = 0): def incr(): nonlocal start #分别保存每一个变量的临时值.类似yield start += 1 return start re ...

  2. 自述:自学Java应该注意什么问题?

    Hello,大家好,我是若风,我是一名IT从业者,纵观当今局势,国内IT互联网行业发展是比较好的,当然学IT技术的人员也特别多,网上的学习资源也非常多,现在有很多人在学技术,想想要进入到 IT这个行业 ...

  3. C语言中的数据转换和定义常量

    一.数据转换 1.数据类型转换:C 语言中如果一个表达式中含有不同类型的常量和变量,在计算时,会将它们自动转换为同一种类型:在 C 语言中也可以对数据类型进行强制转换: 2.自动转换规则: a)浮点数 ...

  4. 笨办法学习python3练习代码:argv参数变量与文件操作

    ex15.py 完成ex15.py需要在ex15.py同文件夹目录下面准备一个txt文件(ex15_sample.txt) 执行ex15.py 如: python     ex15.py      e ...

  5. 【NOIP2015】斗地主 题解(DFS+贪心)

    题目描述 牛牛最近迷上了一种叫斗地主的扑克游戏.斗地主是一种使用黑桃.红心.梅花.方片的AAA到KKK加上大小王的共545454张牌来进行的扑克牌游戏.在斗地主中,牌的大小关 系根据牌的数码表示如下: ...

  6. Spring Cloud Data Flow用Shell来操作,方便建立CICD

    1 前言 欢迎访问南瓜慢说 www.pkslow.com获取更多精彩文章! 之前我们用两篇文章讲解了Spring Cloud Data Flow,例子都是用UI操作的,但我们在Linux系统上经常是无 ...

  7. python2.5项目:个税计算器程序

    #开发个税计算器:应纳税所得额=工资收入金额-各项社会保险费-起征点(5000元)应纳税额=应纳税所得额*税率—速算扣除数(税率参考图片)m=float(input("请输入你的税前工资:& ...

  8. Linux输出缓存你知道多大吗?

    今天看到这个代码很简单,就是验证一下Linux系统的输出缓存大小.当 猜一下这个代码的输出: #include <stdio.h> #include <string.h> #i ...

  9. GitLab CI/CD 配置

    GitLab CI/CD 配置 概念 持续集成的相关概念,可以看这篇文章 持续集成是什么? - 阮一峰的网络日志 操作示例 创建测试项目 sample-web,然后打开项目的 Runners 配置 找 ...

  10. 商业分析-04行为&业务相关数据指标

    [访问深度]用户对产品的了解程度 [弹出率] 弹出率是基于访问回话的 而不是基于页面的,上图中1 4 6 是属于弹出