java concurrency: ThreadLocal及其实现机制

时间:2022-01-11 16:12:25

转载: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相当于一个帮助类,为每个访问的线程建立本地的拷贝数据。