转载:http://shmilyaw-hotmail-com.iteye.com/blog/1703382

ThreadLocal概念

从字面上来理解ThreadLocal,感觉就是相当于线程本地的。我们都知道,每个线程在jvm的虚拟机里都分配有自己独立的空间,线程之间对于本地的空间是相互隔离的。那么ThreadLocal就应该是该线程空间里本地可以访问的数据了。ThreadLocal变量高效地为每个使用它的线程提供单独的线程局部变量值的副本。每个线程只能看到与自己相联系的值,而不知道别的线程可能正在使用或修改它们自己的副本。

很多人看到这里会容易产生一种错误的印象,感觉是不是这个ThreadLocal对象建立了一个类似于全局的map,然后每个线程作为map的key来存取对应线程本地的value。你看,每个线程不一样,所以他们映射到map中的key应该也不一样。实际上,如果我们后面详细分析ThreadLocal的代码时,会发现不是这样的。它具体是怎么实现的呢?后面的详细实现分析部分会讲到这个部分。先别急,看看它是怎么用的吧。

应用和好处

我们在多线程的开发中,经常会考虑到的策略是对一些需要公开访问的属性通过设置同步的方式来访问。这样每次能保证只有一个线程访问它,不会有冲突。但是这样做的结果会使得性能和对高并发的支持不够。在某些情况下,如果我们不一定非要对一个变量共享不可,而是给每个线程一个这样的资源副本,让他们可以独立都各自跑各自的,这样不是可以大幅度的提高并行度和性能了吗?

还有的情况是有的数据本身不是线程安全的,或者说它只能被一个线程使用,不能被其他线程同时使用。如果等一个线程使用完了再给另外一个线程使用就根本不现实。这样的情况下,我们也可以考虑用ThreadLocal。一个典型的情况就是我们连接数据库的时候通常会用到连接池。而对数据库的连接不能有多个线程共享访问。这个时候就需要使用ThreadLocal了。一个典型的用法如下:

  1. private static ThreadLocal<Connection> connectionHolder =
  2. new ThreadLocal<Connection>() {
  3. public Connection initialValue() {
  4. return DriverManager.getConnection(DB_URL);
  5. }
  6. };
  7. pubic static Connection getConnection() {
  8. return connectionHolder.get();
  9. }

ThreadLocal类本身定义了有get(), set()和initialValue()三个方法。前面两个方法是public的,initialValue()是protected的,主要用于我们在定义ThreadLocal对象的时候根据需要来重写。这样我们初始化这么一个对象在里面设置它的初始值时就用到这个方法。

ThreadLocal变量因为本身定位为要被多个线程来访问,它通常被定义为static变量。除了这个示例,在一些开源的j2ee容器以及spring框架中都有应用到。网上可以找到大量介绍的东西,这里就不在赘述。

具体实现细节分析

Thread和ThreadLocal的关系

好吧,现在进入刨根究底时间。ThreadLocal它到底是怎么实现的呢?我们先看看Thread本身的定义。在Thread.java的声明代码中,我们可以看到有这么一部分代码:

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

这就说明了其实每个Thread本身就包含了两个ThreadLocalMap对象的引用。这一点非常重要。以后每个thread要访问他们的local对象时,就是访问存在这个ThreadLocalMap里的value。

ThreadLocalMap

那么这个ThreadLocalMap是个什么东西呢?从字面上可以猜出来,它是一个map。没错,一个map。在ThreadLocal.java中,它是一个内部类。它是以ThreadLocal为key,我们存储的对象为Value的map. 下面是它被删节后的部分定义代码:

  1. static class ThreadLocalMap {
  2. /**
  3. * The entries in this hash map extend WeakReference, using
  4. * its main ref field as the key (which is always a
  5. * ThreadLocal object).  Note that null keys (i.e. entry.get()
  6. * == null) mean that the key is no longer referenced, so the
  7. * entry can be expunged from table.  Such entries are referred to
  8. * as "stale entries" in the code that follows.
  9. */
  10. static class Entry extends WeakReference<ThreadLocal> {
  11. /** The value associated with this ThreadLocal. */
  12. Object value;
  13. Entry(ThreadLocal k, Object v) {
  14. super(k);
  15. value = v;
  16. }
  17. }
  18. /**
  19. * The initial capacity -- MUST be a power of two.
  20. */
  21. private static final int INITIAL_CAPACITY = 16;
  22. /**
  23. * The table, resized as necessary.
  24. * table.length MUST always be a power of two.
  25. */
  26. private Entry[] table;
  27. /**
  28. * The number of entries in the table.
  29. */
  30. private int size = 0;
  31. /**
  32. * The next size value at which to resize.
  33. */
  34. private int threshold; // Default to 0
  35. /**
  36. * Set the resize threshold to maintain at worst a 2/3 load factor.
  37. */
  38. private void setThreshold(int len) {
  39. threshold = len * 2 / 3;
  40. }
  41. /**
  42. * Increment i modulo len.
  43. */
  44. private static int nextIndex(int i, int len) {
  45. return ((i + 1 < len) ? i + 1 : 0);
  46. }
  47. /**
  48. * Decrement i modulo len.
  49. */
  50. private static int prevIndex(int i, int len) {
  51. return ((i - 1 >= 0) ? i - 1 : len - 1);
  52. }
  53. /**
  54. * Construct a new map initially containing (firstKey, firstValue).
  55. * ThreadLocalMaps are constructed lazily, so we only create
  56. * one when we have at least one entry to put in it.
  57. */
  58. ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
  59. table = new Entry[INITIAL_CAPACITY];
  60. int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
  61. table[i] = new Entry(firstKey, firstValue);
  62. size = 1;
  63. setThreshold(INITIAL_CAPACITY);
  64. }
  65. }

这里面牵涉到一个map的实现细节。这里面封装了一个Entry的列表,在Entry里存放的就是key和value。具体是如何从key映射到value的方法和通用的HashMap实现方法类似,在这里就不在赘述。主要知道有了这么一个map,我们给它一个ThreadLocal的对象,它就可以找到对应的value.

从get()入手

我们看看get方法的实现以及它关联的方法:

  1. public T get() {
  2. Thread t = Thread.currentThread(); // 获得当前的线程
  3. ThreadLocalMap map = getMap(t);  //取得当前线程关联的map
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);
  6. if (e != null)
  7. return (T)e.value;
  8. }
  9. return setInitialValue();
  10. }

在代码中间我增加了一些注释。这里比较有意思的一个地方就是getMap()方法。我们首先在获得当前线程的情况下,然后去取得当前线程的ThreadLocalMap。getMap方法做的就是取得ThreadLocalMap这个事。它的定义如下:

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

看到这里,我想各位已经明白了。原来get方法就是获取到当前的线程,在找到这个线程本身关联的map来折腾。你想想,既然每个线程都有各自独立的map,我也只是针对线程本身的map来操作,肯定相互之间不会有干扰了。

get()方法后面的map.getEntry()方法,无疑就是通过map来取这个对应的封装值了。Entry的实现里对这个要访问的值做了一点封装,所以后面返回的是e.value.map.getEntry()方法的实现如下:

  1. private Entry getEntry(ThreadLocal key) {
  2. int i = key.threadLocalHashCode & (table.length - 1);
  3. Entry e = table[i];
  4. if (e != null && e.get() == key)
  5. return e;
  6. else
  7. return getEntryAfterMiss(key, i, e);
  8. }

它就是一个查找和映射的过程,具体的细节和HashMap差不多,这里就不做重点说了。

我们再来看后面的return setInitialValue();这一句是在如果前面找到的map为空或者找到的映射实体为空的话,我们会来设置它的初始值。setInitialValue的定义如下:

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

它调用initialValue方法获得初始值。然后判断情况,是我们映射的实体为空呢还是map为空,如果实体为空的话,我们就直接根据得到的初始值给它设上去,否则我们就新建一个map。createMap()的方法就比较简单,就是一个直接的new:

  1. void createMap(Thread t, T firstValue) {
  2. t.threadLocals = new ThreadLocalMap(this, firstValue);
  3. }

从前面这两部分的代码,我们可以看到。ThreadLocal中的get方法在首先调用get()方法的时候,会去调用initialValue()方法获取一下初始值。这也就是为什么前面说到推荐我们覆写initialValue()方法来设置自己期望的值。另外,在这里也会为每个线程建立它本地的map对象。

再看set()

把前面get()方法的流程理清之后,再来看set方法。感觉就几乎没什么好说的了。它的实现代码如下:

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

基本上和get()的差不多,就不啰嗦了。

一个有意思的地方

为什么要通常将ThreadLocal对象声明为static的呢?一方面是为了多个线程共享方便。另外,由于每个Thread都有这么个ThreadLocalMap对象的引用,每次在ThreadLocal中执行Get方法的时候,实际上就是根据当前线程来获取它的ThreadLocalMap对象。再将这个ThreadLocal对象为Key来查找对应的值。因为每个Thread各自的ThreadLocalMap,所以相当于每个对象对应这么一个同样的ThreadLocal对象key值,来放一份自己本身的拷贝。

我们可能还有一个疑问就是既然如果我们声明一个ThreadLocal对象相当于每个关联访问的Thread有了一个该对象对应的key和value对,为什么每个对象要放这么一个Map呢?这是考虑到如果有多个ThreadLocal对象在被多个线程使用的情况。ThreadLocal类中间有这么一部分代码:

  1. private final int threadLocalHashCode = nextHashCode();
  2. /**
  3. * The next hash code to be given out. Updated atomically. Starts at
  4. * zero.
  5. */
  6. private static AtomicInteger nextHashCode =
  7. new AtomicInteger();
  8. /**
  9. * The difference between successively generated hash codes - turns
  10. * implicit sequential thread-local IDs into near-optimally spread
  11. * multiplicative hash values for power-of-two-sized tables.
  12. */
  13. private static final int HASH_INCREMENT = 0x61c88647;
  14. /**
  15. * Returns the next hash code.
  16. */
  17. private static int nextHashCode() {
  18. return nextHashCode.getAndAdd(HASH_INCREMENT);
  19. }

ThreadLocal中的static变量nextHashCode相当于一个全局的私有变量,但是threadLocalHashCode是针对每个对象的实例成员,每次它被初始化的时候都要调用nextHashCode()方法。这个方法是static的,在成员初始化的时候也就运行一次。它就是为了将这个值增加一段,保证这个对象的threadLocalHashCode和其他ThreadLocal对象的不一样。因为最终将ThreadLocal对象映射到map中的值是用的threadLocalHashCode。所以,当我们多个线程要访问多个ThreadLocal变量的时候,每个变量映射到的就是ThreadLocalMap中不同的项。

总结

每个线程都有一个map,这个map里存的就是和该线程关联的本地数据。可能这个map是空的。在通过访问ThreadLocal的方法时,通过和ThreadLocal对象建立关联来映射到对应的本地对象。这个ThreadLocal对象相当于是map的key,放的本地变量的值相当于map里的value.ThreadLocal相当于一个帮助类,为每个访问的线程建立本地的拷贝数据。

java concurrency: ThreadLocal及其实现机制的更多相关文章

  1. Java Concurrency - ThreadLocal, 本地线程变量

    共享数据是多线程应用最常见的问题之一,但有时我们需要为每个线程保存一份独立的变量.Java API 提供了 ThreadLocal 来解决这个问题. 一个 ThreadLocal 作用的例子: imp ...

  2. 深入浅出 Java Concurrency (15): 锁机制 part 10 锁的一些其它问题

      主要谈谈锁的性能以及其它一些理论知识,内容主要的出处是<Java Concurrency in Practice>,结合自己的理解和实际应用对锁机制进行一个小小的总结. 首先需要强调的 ...

  3. 深入浅出 Java Concurrency (15): 锁机制 part 10 锁的一些其它问题[转]

    主要谈谈锁的性能以及其它一些理论知识,内容主要的出处是<Java Concurrency in Practice>,结合自己的理解和实际应用对锁机制进行一个小小的总结. 首先需要强调的一点 ...

  4. java并发:线程同步机制之ThreadLocal

    1.简述ThreadLocal ThreadLocal实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程.ThreadLocal是一个线程级别的局部变量 ...

  5. Java 内存区域和GC机制分析

    目录 Java垃圾回收概况 Java内存区域 Java对象的访问方式 Java内存分配机制 Java GC机制 垃圾收集器 Java垃圾回收概况 Java GC(Garbage Collection, ...

  6. Java中ThreadLocal的设计与使用

    早在Java 1.2推出之时,Java平台中就引入了一个新的支持:java.lang.ThreadLocal,给我们在编写多线程程序时提供了一种新的选择.使用这个工具类可以很简洁地编写出优美的多线程程 ...

  7. 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则

    转: http://www.blogjava.net/xylz/archive/2010/07/03/325168.html 在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到 ...

  8. 【转载】Java系列笔记(3) - Java 内存区域和GC机制

    Java系列笔记(3) - Java 内存区域和GC机制 转载:原文地址http://www.cnblogs.com/zhguang/p/3257367.html 目录 Java垃圾回收概况 Java ...

  9. ThreadLocal ——android消息机制handler在非主线程创建not called Looper.prepare() 错误的原因

    引用自:https://www.jianshu.com/p/a8fa72e708d3 引出: 使用Handler的时候,其必须要跟一个Looper绑定.在UI线程可直接初始化Handler来使用.但是 ...

随机推荐

  1. AndroidStudio 使用Hide API

    1.反射法 速度慢 2.生成新的android.jar 通常需要隐藏API的地方并不多 不需要整个都编译 而且编译出的framework.jar也不全 缺少java.*和javax.* 所以只把需要的 ...

  2. Qt实现嵌入桌面的半透明窗口 good

    这儿用上了前面一文提到的函数findDesktopIconWnd().见: http://mypyg.blog.51cto.com/820446/263349 一.将Qt窗口嵌入到桌面中.声明一个最简 ...

  3. ProFTPD 初探

    ProFTPD:一个Unix平台上或是类Unix平台上(如Linux, FreeBSD等)的FTP服务器程序.

  4. openStack deep dive,Retake Policy

    Method of Payment: visa MasterCard American Express Discover

  5. ExpandableListView(三)只展开一个group,没有child不展开group

    本文是自己在实践中,发现的问题. 有时候想让界面更加的人性化,就要实现很多的效果,比如只展开一个group,在点击下个group的同时,关闭之前的group 在一个ExpandableListView ...

  6. wordpress在window下完美实现301重定向的方法

    问题: 首先,简单说一下关于301重定向的问题,最简单的理解就是,假设你的主机上绑定有 www.uilike.cn, uilike.cn, www.uiseo.cn三个域名,当你想输入 uilike. ...

  7. Objective-c 中的变量

    OC中的语言变量,按作用域可分为两种:局部变量和全局变量. 局部变量:也称为内部变量,局部变量是在方法内部声明的.其作用域仅限于方法内,离开该方法再使用这个变量就是非法的. 全局变量:也称为外部变量, ...

  8. Assertion failure in -[UIView layoutSublayersOfLayer:]

    Assertion failure in -[UIView layoutSublayersOfLayer:], /SourceCache/UIKit/UIKit-2935.137/UIView.m:8 ...

  9. 浅谈JSP(二)

    一.EL表达式 作用:从作用域(pageContext,request,session,application)中取值,并显示在页面中. 本质:用于替换输出脚本(<%= %>). 1.从作 ...

  10. C++读写文件的简单例子

    #include <iostream> #include <fstream> using namespace std; void main() { ofstream in; i ...