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. package com.test1;
  2.  
  3. import java.util.HashMap;
  4. import java.util.Map;
  5.  
  6. public class Test1 implements Runnable {
  7. private final static Map map = new HashMap();
  8. int id;
  9. /*static ThreadLocal<HashMap> threadLocal = new ThreadLocal<HashMap>(){
  10.  
  11. @Override
  12.  
  13. protected HashMap initialValue() {
  14.  
  15. System.out.println(Thread.currentThread().getName()+"initialValue");
  16.  
  17. return new HashMap();
  18.  
  19. }
  20.  
  21. };*/
  22. public Test1(int id) {
  23. super();
  24. this.id = id;
  25. }
  26.  
  27. @Override
  28. public void run() {
  29. //Map map = threadLocal.get();
  30. for (int i = 0; i < 20; i++) {
  31. map.put(i, i + id * 100);
  32. try {
  33. Thread.sleep(100);
  34. } catch (Exception ex) {
  35. }
  36. }
  37. System.out.println(Thread.currentThread().getName() + "# map.size()="
  38. + map.size() + " # " + map);
  39. }
  40.  
  41. public static void main(String[] args) {
  42. Thread[] runs = new Thread[15];
  43. Test1 t = new Test1(1);
  44. for (int i = 0; i < runs.length; i++) {
  45. runs[i] = new Thread(t);
  46. }
  47. for (int i = 0; i < runs.length; i++) {
  48. runs[i].start();
  49. }
  50. }
  51. }

这段程序的本意是,启动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.  
  17. return setInitialValue();
  18.  
  19. }

getMap方法的源码:

  1. ThreadLocalMap getMap(Thread t) {
  2.  
  3. return t.threadLocals;
  4.  
  5. }

该方法返回的是当前线程中的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.  
  19. }

我们看到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.  
  15. }

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

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

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

  1. /*ThreadLocal的hash值,map用它来存储值*/
  2.  
  3. private final int threadLocalHashCode = nextHashCode();
  4.  
  5. /*改类能以原子的方式更新int值,这里主要是在产生新的ThreadLocal实例时用来产生一个新的hash值,map用该值来存储对象*/
  6.  
  7. private static AtomicInteger nextHashCode =
  8.  
  9. new AtomicInteger();
  10.  
  11. /*该变量标识每次产生新的ThreadLocal实例时,hash值的增量*/
  12.  
  13. 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.  
  33. if (k == null) {
  34.  
  35. replaceStaleEntry(key, value, i);
  36.  
  37. return;
  38.  
  39. }
  40.  
  41. }
  42.  
  43. tab[i] = new Entry(key, value);
  44.  
  45. int sz = ++size;
  46.  
  47. if (!cleanSomeSlots(i, sz) && sz >= threshold)
  48.  
  49. rehash();
  50.  
  51. }

这个方法的主要功能就是讲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. }
  14.  
  15. }

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

总结

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

Java ThreadLocal 使用详解的更多相关文章

  1. 最强Java并发编程详解:知识点梳理,BAT面试题等

    本文原创更多内容可以参考: Java 全栈知识体系.如需转载请说明原处. 知识体系系统性梳理 Java 并发之基础 A. Java进阶 - Java 并发之基础:首先全局的了解并发的知识体系,同时了解 ...

  2. Java 字符串格式化详解

    Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...

  3. Java 序列化Serializable详解

    Java 序列化Serializable详解(附详细例子) Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化Serialization(序列化)是一种将对象以一连 ...

  4. Java String类详解

    Java String类详解 Java字符串类(java.lang.String)是Java中使用最多的类,也是最为特殊的一个类,很多时候,我们对它既熟悉又陌生. 类结构: public final ...

  5. 最新java数组的详解

    java中HashMap详解 http://alex09.iteye.com/blog/539545 总结: 1.就像引用类型的数组一样,当我们把 Java 对象放入数组之时,并不是真正的把 Java ...

  6. JAVA IO 类库详解

    JAVA IO类库详解 一.InputStream类 1.表示字节输入流的所有类的超类,是一个抽象类. 2.类的方法 方法 参数 功能详述 InputStream 构造方法 available 如果用 ...

  7. 转:Java HashMap实现详解

    Java HashMap实现详解 转:http://beyond99.blog.51cto.com/1469451/429789 1.    HashMap概述:    HashMap是基于哈希表的M ...

  8. 淘宝JAVA中间件Diamond详解(2)-原理介绍

    淘宝JAVA中间件Diamond详解(二)---原理介绍 大家好,通过第一篇的快速使用,大家已经对diamond有了一个基本的了解.本次为大家带来的是diamond核心原理的介绍,主要包括server ...

  9. 【转】 java中HashMap详解

    原文网址:http://blog.csdn.net/caihaijiang/article/details/6280251 java中HashMap详解 HashMap 和 HashSet 是 Jav ...

随机推荐

  1. LoadRunner脚本回放与设置

    一.runtime setting 1.迭代次数设置与迭代步长(循环间隔时间) 2.日志打印设置       二.实时观看回放 1.动态回放与静态回放(静态回放时,不会有逐行高亮显示:动态回放时高亮显 ...

  2. Java对Redis基本使用

    1 引入jar包 java是通过Jedis对redis进行操作的,首先引入jedis.jar <dependency> <groupId>redis.clients</g ...

  3. android 开源

    http://blog.csdn.net/xiaoxiao_job/article/details/45196119?ref=myread MPAndroidChart https://github. ...

  4. WOJ600——水题

    第一次做本校OJ的题,被坑的好惨啊! 题目:600.Minimum  Distance 题目大意:给定平面上3个点A.B.C,求平面上的任一顶点P,使得|PA|+2|PB|+3|PC|. 由于刚好在这 ...

  5. python 实现代理服务器

    # encoding:utf-8 import socket import thread import re def getAddr(d): a = re.search("Host: (.* ...

  6. 第1节 yarn:14、yarn集群当中的三种调度器

    yarn当中的调度器介绍: 第一种调度器:FIFO Scheduler  (队列调度器) 把应用按提交的顺序排成一个队列,这是一个先进先出队列,在进行资源分配的时候,先给队列中最头上的应用进行分配资源 ...

  7. [Python3网络爬虫开发实战] 1.2.3-ChromeDriver的安装

    前面我们成功安装好了Selenium库,但是它是一个自动化测试工具,需要浏览器来配合使用,本节中我们就介绍一下Chrome浏览器及ChromeDriver驱动的配置. 首先,下载Chrome浏览器,方 ...

  8. linux性能优化cpu-01性能指标

    学习性能优化的第一步,一定要了解性能指标. 性能指标是什么? 当我们看到性能指标时一定先想到“高并发”.“响应快”,这个两个指标也对应着性能优化的两个核心指标—— “吞吐率”和“低延迟”. 这两个指标 ...

  9. poj 2186 强连通分量

    poj 2186 强连通分量 传送门 Popular Cows Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 33414 Acc ...

  10. JDK的安装和环境变量配置

    1.安装JDK开发环境 下载网站: http://www.oracle.com/technetwork/java/javase/downloads/index.html 进入后选择Accept Lic ...