用法:
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. Lua学习入门(代码块)

    ). if then else if a < then b = else b = end ). if elseif else then if a < then b = elseif a = ...

  2. A - A Simple Problem with Integers (线段树的区间修改与区间查询)

    You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of op ...

  3. Django学习路6_修改数据库为 mysql ,创建mysql及进行迁徙

    在项目的 settings 中修改 DATABASES = { 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os. ...

  4. PHP date_timestamp_get() 函数

    实例 返回今天的日期和时间的 Unix 时间戳: <?php$date=date_create();echo date_timestamp_get($date);?> 运行实例 » 定义和 ...

  5. ABP使用Nginx代理导致租户ID(Abp.TenantId)丢失

    描述:ABP使用Nginx代理导致租户ID(Abp.TenantId)丢失,自定义header无效无法传递,导致租户选择认证失败.原因是因为 Nginx 过滤是“.”这符号. 解决: 1,先从代码人手 ...

  6. SpringMvc异常处理和SpringMvc拦截器

    1. 异常处理思路 Controller调用service,service调用dao,异常都是向上抛出的,最终有DispatcherServlet找异常处理器进 行异常的处理. SpringMVC的异 ...

  7. 【每日一个小技巧】Python | input的提示信息换行输出,提示信息用变量表示

    [每日一个小技巧]Python | input的提示信息换行输出,提示信息用变量表示 在书写代码的途中,经常会实现这样功能: 请输入下列选项前的序号: 1.选择1 2.选择2 3.选择3 在pytho ...

  8. 付费?是不可能的!20行Python代码实现一款永久免费PDF编辑工具

    PDF(Portable Document Format),中文名称便携文档格式是我们经常会接触到的一种文件格式,文献.文档…很多都是PDF格式.它以格式稳定的优势,使得我们在打印.分享.传输过程中能 ...

  9. 我给这个Python库打101分!

    日志在开发过程中是一种被很多程序员 不重视 ,但是却 至关重要 的一项功能. 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案 ...

  10. Linux学习笔记之如何把ubuntu下的c文件共享到windows下文件夹

    1.首先可在桌面新建文件夹,或者自己任意路径文件夹都可以, 2.在Linux下点击虚拟机 点击设置 点击选项,选择共享文件夹,点击总是启用,然后添加文件夹,添加你windows下想保存的文件夹   可 ...