开发者社区> 行者武松> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Java ThreadLocal 使用详解

简介:
+关注继续查看

引言

ThreadLocal的官方API解释为:

“该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副 本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。”

大概的意思有两点:

  1. ThreadLocal提供了一种访问某个变量的特殊方式:访问到的变量属于当前线程,即保证每个线程的变量不一样,而同一个线程在任何地方拿到的变量都是一致的,这就是所谓的线程隔离。

  2. 如果要使用ThreadLocal,通常定义为private static类型,在我看来最好是定义为private static final类型。

应用场景

ThreadLocal通常用来共享数据,当你想在多个方法中使用某个变量,这个变量是当前线程的状态,其它线程不依赖这个变量,你第一时间想到的 就是把变量定义在方法内部,然后再方法之间传递参数来使用,这个方法能解决问题,但是有个烦人的地方就是,每个方法都需要声明形参,多处声明,多处调用。 影响代码的美观和维护。有没有一种方法能将变量像private static形式来访问呢?这样在类的任何一处地方就都能使用。这个时候ThreadLocal大显身手了。

实践

我们首先来看一段代码:


  1. mport java.util.HashMap; 
  2.  
  3. import java.util.Map; 
  4.  
  5. public class TreadLocalTest { 
  6.  
  7. // static ThreadLocal<HashMap> threadLocal = new ThreadLocal<HashMap>(){ 
  8.  
  9. // @Override 
  10.  
  11. // protected HashMap initialValue() { 
  12.  
  13. // System.out.println(Thread.currentThread().getName()+”initialValue”); 
  14.  
  15. // return new HashMap(); 
  16.  
  17. // } 
  18.  
  19. // }; 
  20.  
  21. public static class T1 implements Runnable { 
  22.  
  23. private final static Map map = new HashMap(); 
  24.  
  25. int id; 
  26.  
  27. public T1(int id) { 
  28.  
  29. this.id = id; 
  30.  
  31.  
  32. public void run() { 
  33.  
  34. // Map map = threadLocal.get(); 
  35.  
  36. for (int i = 0; i < 20; i++) { 
  37.  
  38. map.put(i, i + id * 100); 
  39.  
  40. try { 
  41.  
  42. Thread.sleep(100); 
  43.  
  44. catch (Exception ex) { 
  45.  
  46.  
  47.  
  48. System.out.println(Thread.currentThread().getName() 
  49.  
  50. + “# map.size()=” + map.size() + ” # ” + map); 
  51.  
  52.  
  53.  
  54. public static void main(String[] args) { 
  55.  
  56. Thread[] runs = new Thread[15]; 
  57.  
  58. T1 t = new T1(1); 
  59.  
  60. for (int i = 0; i < runs.length; i++) { 
  61.  
  62. runs[i] = new Thread(t); 
  63.  
  64.  
  65. for (int i = 0; i < runs.length; i++) { 
  66.  
  67. runs[i].start(); 
  68.  
  69.  
  70.  

这段程序的本意是,启动15个线程,线程向map中写入20个整型值,然后输出map。运行该程序,观察结果,我们会发现,map中压根就不止20个元素,这说明程序产生了线程安全问题。

我们都知道HashMap是非线程安全的,程序启动了15个线程,他们共享了同一个map,15个线程都往map写对象,这势必引起线程安全问题。

我们有两种方法解决这个问题:

  1. 将map的声明放到run方法中,这样map就成了方法内部变量,每个线程都有一份new HashMap(),无论多少个线程执行run方法,都不会有线程安全问题。这个方法也正如应用场景中提到的,如果有多处地方使用到map,传值是个烦人的地方。

  2. 将HashMap换成Hashtable。用线程同步来解决问题,然而我们的程序只是想向一个map中写入20个整型的KEY-VALUE而已,并不需要线程同步,同步势必影响性能,得不偿失。

  3. ThreadLocal提供另外一种解决方案,即在解决方案a上边,将new HashMap()得到的实例变量,绑定到当前线程中。之后从任何地方,都可以通过ThreadLocal获取到该变量。将程序中的注释代码恢复,再将 private final static Map map = new HashMap();注释掉,运行程序,结果就是我们想要的。

实现原理

程序调用了get()方法,我们来看一下该方法的源码:


  1. public T get() { 
  2.  
  3. Thread t = Thread.currentThread(); 
  4.  
  5. ThreadLocalMap map = getMap(t); 
  6.  
  7. if (map != null) { 
  8.  
  9. ThreadLocalMap.Entry e = map.getEntry(this); 
  10.  
  11. if (e != null
  12.  
  13. return (T)e.value; 
  14.  
  15.  
  16. return setInitialValue(); 
  17.  
  18.  
  19. getMap方法的源码: 
  20.  
  21. ThreadLocalMap getMap(Thread t) { 
  22.  
  23. return t.threadLocals; 
  24.  

该方法返回的是当前线程中的ThreadLocalMap实例。阅读Thread的源码我们发现Thread中有如下变量声明:


  1. /* ThreadLocal values pertaining to this thread. This map is maintained 
  2.  
  3. * by the ThreadLocal class. */ 
  4.  
  5. ThreadLocal.ThreadLocalMap threadLocals = null

我们暂时可以将ThreadLocalMap理解为一个类似Map的这么个类,之后再讲解它。

get()方法的大致意思就是从当前线程中拿到ThreadLocalMap的实例threadLocals,如果threadLocals不为 空,那么就以当前ThreadLocal实例为KEY从threadLocals中拿到对应的VALUE。如果不为空,那么就调用 setInitialValue()方法初始化threadLocals,最终返回的是initialValue()方法的返回值。下面是 setInitialValue()方法的源码


  1. private T setInitialValue() { 
  2.  
  3. T value = initialValue(); 
  4.  
  5. Thread t = Thread.currentThread(); 
  6.  
  7. ThreadLocalMap map = getMap(t); 
  8.  
  9. if (map != null
  10.  
  11. map.set(this, value); 
  12.  
  13. else 
  14.  
  15. createMap(t, value); 
  16.  
  17. return value; 
  18.  

我们看到map.set(this, value);这句代码将ThreadLocalMap的实例作为KEY,将initialValue()的返回值作为VALUE,set到了threadLocals中。

程序在声明ThreadLocal实例的时候覆写了initialValue(),返回了VALUE,当然我们可以直接调用set(T t)方法来设置VALUE。下面是set(T t)方法的源码:


  1. public void set(T value) { 
  2.  
  3. Thread t = Thread.currentThread(); 
  4.  
  5. ThreadLocalMap map = getMap(t); 
  6.  
  7. if (map != null
  8.  
  9. map.set(this, value); 
  10.  
  11. else 
  12.  
  13. createMap(t, value); 
  14.  

我们看到它比setInitialValue()方法就少了个return语句。这两种方式都能达到初始化ThreadLocalMap实例的效果。

我们再来看一下ThreadLocal类的结构。

ThreadLocal类只有三个属性,如下:


  1. /*ThreadLocal的hash值,map用它来存储值*/ 

private final int threadLocalHashCode = nextHashCode();

/*改类能以原子的方式更新int值,这里主要是在产生新的ThreadLocal实例时用来产生一个新的hash值,map用该值来存储对象*/


  1. private static AtomicInteger nextHashCode = 
  2. new AtomicInteger(); 

/*该变量标识每次产生新的ThreadLocal实例时,hash值的增量*/

private static final int HASH_INCREMENT = 0x61c88647;

剩下的就是一些方法。最关键的地方就是ThreadLocal定义了一个静态内部类ThreadLocalMap。我们在下一章节再来分析这个类。 从ThreadLocal的类结构,我们可以看到,实际上问题的关键先生是ThreadLocalMap,ThreadLocal只是提供了管理的功能, 我们也可以说ThreadLocal只是代理了ThreadLocalMap而已。

ThreadLocalMap源码分析

既然ThreadLocalMap实现了类似map的功能,那我们首先来看看它的set方法源码:


  1. private void set(ThreadLocal key, Object value) { 
  2.  
  3. // We don’t use a fast path as with get() because it is at 
  4.  
  5. // least as common to use set() to create new entries as 
  6.  
  7. // it is to replace existing ones, in which case, a fast 
  8.  
  9. // path would fail more often than not. 
  10.  
  11. Entry[] tab = table; 
  12.  
  13. int len = tab.length; 
  14.  
  15. int i = key.threadLocalHashCode & (len-1); 
  16.  
  17. for (Entry e = tab[i]; 
  18.  
  19. e != null
  20.  
  21. e = tab[i = nextIndex(i, len)]) { 
  22.  
  23. ThreadLocal k = e.get(); 
  24.  
  25. if (k == key) { 
  26.  
  27. e.value = value; 
  28.  
  29. return
  30.  
  31.  
  32. if (k == null) { 
  33.  
  34. replaceStaleEntry(key, value, i); 
  35.  
  36. return
  37.  
  38.  
  39.  
  40. tab[i] = new Entry(key, value); 
  41.  
  42. int sz = ++size; 
  43.  
  44. if (!cleanSomeSlots(i, sz) && sz >= threshold) 
  45.  
  46. rehash(); 
  47.  

这个方法的主要功能就是讲KEY-VALUE存储到ThreadLocalMap中,这里至少我们看到KEY实际上是 key.threadLocalHashCode,ThreadLocalMap同样维护着Entry数组,这个Entry我们在下一节会讲解。这里涉及 到了Hash冲突的处理,这里并不会向HashMap一样冲突了以链表的形式往后添加。如果对这个Hash冲突解决方案有兴趣,可以再进一步研究源码。

既然ThreadLocalMap也是用Entry来存储对象,那我们来看看Entry类的声明,Entry被定义在ThreadLocalMap的内部:


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

这里我们看到Entry集成了WeakReference类,泛型声明了ThreadLocal,即每一个Entry对象都保留了对 ThreadLocal实例的弱引用,之所以这么干的原因是,线程在结束之后需要将ThreadLocal实例从map中remove调,以便回收内存空 间。

总结

首先,ThreadLocalMap并不是为了解决线程安全问题,而是提供了一种将实例绑定到当前线程的机制,类似于隔离的效果,实际上自己在方法 中new出来变量也能达到类似的效果。ThreadLocalMap跟线程安全基本不搭边,绑定上去的实例也不是多线程公用的,而是每个线程new一份, 这个实例肯定不是共用的,如果共用了,那就会引发线程安全问题。ThreadLocalMap最大的用处就是用来把实例变量共享成全局变量,在程序的任何 方法中都可以访问到该实例变量而已。网上很多人说ThreadLocalMap是解决了线程安全问题,其实是望文生义,两者不是同类问题。


来源:51CTO

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
【底层原理之旅—ThreadLocal深入浅出的源码分析|Java 刷题打卡
【底层原理之旅—ThreadLocal深入浅出的源码分析|Java 刷题打卡
0 0
Android(Java) | 如何使程序实现线程安全(拓展分析:ThreadLocal、重排序、volatile/final)
Android(Java) | 如何使程序实现线程安全(拓展分析:ThreadLocal、重排序、volatile/final)
0 0
Java笔记:ThreadLocal和压力测试(2)
Java笔记:ThreadLocal和压力测试
0 0
Java笔记:ThreadLocal和压力测试(1)
Java笔记:ThreadLocal和压力测试
0 0
Java——多线程高并发系列之ThreadLocal的使用
Java——多线程高并发系列之ThreadLocal的使用
0 0
Java并发编程 - 一段代码解释 ThreadLocal 特性
Java并发编程 - 一段代码解释 ThreadLocal 特性
0 0
Java并发编程 - 线程封闭之 ThreadLocal(三)
Java并发编程 - 线程封闭之 ThreadLocal(三)
0 0
Java并发编程 - 线程封闭之 ThreadLocal(二)
Java并发编程 - 线程封闭之 ThreadLocal(二)
0 0
Java并发编程 - 线程封闭之 ThreadLocal(一)
Java并发编程 - 线程封闭之 ThreadLocal(一)
0 0
《提升能力,涨薪可待》-Java多线程与并发之ThreadLocal
ThreadLocal是线程本地变量,可以为多线程的并发问题提供一种解决方式,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
0 0
+关注
行者武松
杀人者,打虎武松也。
文章
问答
文章排行榜
最热
最新
相关电子书
更多
JAVA 应用排查全景图
立即下载
Java工程师必读手册
立即下载
Java应用提速(速度与激情)
立即下载