JDK 源码解析 —— ThreadLocal

零. 简介
这个类提供本地线程变量。不同于一般的变量,这些变量在他们各自的线程里通过 get、set 访问一个它自己的变量,这是一个独立初始化的变量副本。在一个类中,ThreadLocal 实例一般是 private static 的,期望和一个线程关联状态(如 userId,transactionId 等)。简单地说,就是此类提供了线程的本地变量,线程修改本地变量不互相影响。
 

举个例子,下面的类给每个线程生成一个唯一的标识。一个线程 id 在第一次调用 ThreadId.get() 被赋值,并且在后续的调用上保持不变。

 

[java] view plain copy
 
 print?
  1. public class ThreadId {  
  2.        // Atomic integer containing the next thread ID to be assigned  
  3.        private static final AtomicInteger nextId = new AtomicInteger(0);  
  4.   
  5.        // Thread local 变量保存着每个线程的ID  
  6.        private static final ThreadLocal<Integer> threadId =  
  7.            new ThreadLocal<Integer>() {  
  8.                @Override protected Integer initialValue() {  
  9.                    return nextId.getAndIncrement();  
  10.            }  
  11.        };  
  12.   
  13.        // 返回当前线程的唯一ID,如果没有值的话先赋值  
  14.        public static int get() {  
  15.            return threadId.get();  
  16.        }  
  17. }  


只要线程还存活并且 ThreadLocal 实例可访问,那么每个线程持有一个确定的引用指向本地的变量副本,当线程消失,它的本地变量副本将会被GC(除非还被其他对象引用)。

 

 

 

一. 使用案例

 

[java] view plain copy
 
 print?
  1. package com.wenniuwuren.java.threadlocal;  
  2.   
  3. /** 
  4.  * 执行结果: 
  5.  * Thread[Thread-0,5,main]---------count=0 
  6.  * Thread[Thread-0,5,main]---------count=1 
  7.  * Thread[Thread-0,5,main]---------count=2 
  8.  * Thread[Thread-0,5,main]---------count=3 
  9.  * Thread[Thread-0,5,main]---------count=4 
  10.  * Thread[Thread-2,5,main]---------count=0 
  11.  * Thread[Thread-2,5,main]---------count=1 
  12.  * Thread[Thread-2,5,main]---------count=2 
  13.  * Thread[Thread-1,5,main]---------count=0 
  14.  * Thread[Thread-2,5,main]---------count=3 
  15.  * Thread[Thread-2,5,main]---------count=4 
  16.  * Thread[Thread-1,5,main]---------count=1 
  17.  * Thread[Thread-1,5,main]---------count=2 
  18.  * Thread[Thread-1,5,main]---------count=3 
  19.  * Thread[Thread-1,5,main]---------count=4 
  20.  * 可以看出线程的变量更新没有相互影响 
  21.  * Created by hzzhuyibin on 2017/3/14. 
  22.  */  
  23. public class ThreadLocalTest {  
  24.   
  25.     private static ThreadLocal<Integer> count = new ThreadLocal<Integer>() {  
  26.         public Integer initialValue() {  
  27.             return 0;  
  28.         }  
  29.     };  
  30.   
  31.     public static void main(String[] args) {  
  32.   
  33.         NewThread thread1 = new NewThread(count);  
  34.         NewThread thread2 = new NewThread(count);  
  35.         NewThread thread3 = new NewThread(count);  
  36.   
  37.         thread1.start();  
  38.         thread3.start();  
  39.         thread2.start();  
  40.   
  41.     }  
  42.   
  43.     public static class NewThread extends Thread {  
  44.         ThreadLocal<Integer> threadLocal = null;  
  45.   
  46.         public NewThread(ThreadLocal<Integer> threadLocal) {  
  47.             this.threadLocal = threadLocal;  
  48.         }  
  49.   
  50.         @Override  
  51.         public void run() {  
  52.             for (int i = 0; i < 5; i++) {  
  53.                 System.out.println(Thread.currentThread() + "---------count=" + threadLocal.get());  
  54.                 threadLocal.set(threadLocal.get() + 1);  
  55.             }  
  56.         }  
  57.     }  
  58.   
  59. }  

 

二. 源码分析
ThreadLocal 的数据结构:实线表示强引用,虚线表示弱引用
每个 Thread 维护一个ThreadLocalMap 映射 table,映射 table 的 key 是 ThreadLocal 实例,value 就是线程存独立的变量副本的地方。
为什么这么设计,而不是由 ThreadLocal 来维护一个以 Thread 为 key 的映射呢?原因如下:
  • 减小 Entry 数组大小:ThreadLocal 数量多,还是 Thread 的数量多,显而易见,使用 ThreadLocal 来当 key 可以减少 Entry 数量
  • 减小内存占用:当 Thread 消亡,对 Thread 实例不在引用,则 GC 后就会清除相关数据
1.先看字段含义
[java] view plain copy
 
 print?
  1. /** ThreadLocals 依赖每个线程的线性嗅探哈希映射到每个线程(Thread.threadLocals and inheritableThreadLocals)。 
  2.  * ThreadLocal 对象作为 key,通过 threadLocalHashCode 来搜索。这是一个定制化的减少冲突的哈希码(只在 ThreadLocalMaps 有用), 
  3.  * 其中连续构造的ThreadLocals由相同的线程使用,在较不常见的情况下保持良好行为。 
  4.  */  
  5. private final int threadLocalHashCode = nextHashCode();  
  6.   
  7.  /** 
  8.      * 生成下一个 Hash Code。原子更新。从零开始。 
  9.      */  
  10.     private static AtomicInteger nextHashCode =  
  11.         new AtomicInteger();  
  12.   
  13. /** 
  14.      *连续生成的散列码之间的差异 - 将隐式顺序线程本地ID转换为二次幂表的近似最优扩展的乘法散列值。 
  15.      *简单来说就是偏移量,offetset 
  16.      */  
  17.     private static final int HASH_INCREMENT = 0x61c88647;  
  18.   
  19. /** 
  20.      * 返回下一个 Hash 值 
  21.      */  
  22.     private static int nextHashCode() {  
  23.         return nextHashCode.getAndAdd(HASH_INCREMENT);  
  24.     }  

2.主要方法:
[java] view plain copy
 
 print?
  1. /**   返回当前线程的线程本地变量值。这个方法将会在线程第一次通过 get() 访问变量的时候调用, 
  2.      * 除非这个线程之前调用过 set() 那么 initialValue() 才不会被调用。通常,这个方法只会被调用一次, 
  3.      * 但是它在 get() 后调用 remove(),能被再次调用。 
  4.      * 这里实现是简单返回 null,如果想赋其他值需要重写这个方法。 
  5.      */  
  6.     protected T initialValue() {  
  7.         return null;  
  8.     }  

(1)核心方法 get() 相关内容:
[java] view plain copy
 
 print?
  1. /** 
  2. * 返回当前线程的本地变量副本值。如果这个变量没有值,则返回 initialValue() 初始化的值 
  3. */  
  4. public T get() {  
  5.     Thread t = Thread.currentThread();  
  6.     // ThreadLocalMap 是一个为了保存线程本地变量定制化的 hash map。  
  7.     ThreadLocalMap map = getMap(t);  
  8.     if (map != null) {  
  9.         ThreadLocalMap.Entry e = map.getEntry(this);  
  10.         if (e != null) {  
  11.             @SuppressWarnings("unchecked")  
  12.             T result = (T)e.value;  
  13.             return result;  
  14.         }  
  15.     }  
  16.     return setInitialValue();  
  17. }  

其中的 getMap(Thread t):
[java] view plain copy
 
 print?
  1. /**   获取和ThreadLocal相关的 map。在 InheritableThreadLocal中重写 
  2.  */  
  3. ThreadLocalMap getMap(Thread t) {  
  4.     return t.threadLocals;  
  5. }  

Thread 类中的代码:这里可以看到是 Thread 持有 ThreadLocal.ThreadLocalMap 引用
[java] view plain copy
 
 print?
  1. /* 与此线程有关的ThreadLocal值。 此 map 由ThreadLocal类维护。 */  
  2. dLocal.ThreadLocalMap threadLocals = null;  

其中的 map.getEntry(this):
[java] view plain copy
 
 print?
  1. /** 
  2.  * 获取和key关联的Entry。 此方法本身仅处理快速路径:直接命中现有键。 
  3.  *  这是为了最大限度地提高直接命中的性能,部分通过使这种方法很容易嵌入。 
  4.  */  
  5. private Entry getEntry(ThreadLocal<?> key) {  
  6.     int i = key.threadLocalHashCode & (table.length - 1);  
  7.     Entry e = table[i];  
  8.     // 命中 hash slot  
  9.     if (e != null && e.get() == key)  
  10.         return e;  
  11.     else  
  12.         // 如果在 hash slot 里面没有直接查到就进入这个方法  
  13.         return getEntryAfterMiss(key, i, e);  
  14. }  
  15.   
  16. /** 
  17.  * 如果在 hash slot 里面没有直接查到就进入这个方法 
  18.  */  
  19. private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {  
  20.     Entry[] tab = table;  
  21.     int len = tab.length;  
  22.   
  23.     while (e != null) {  
  24.         ThreadLocal<?> k = e.get();  
  25.         if (k == key)  
  26.             return e;  
  27.         if (k == null) // 清理无用 entry  
  28.             expungeStaleEntry(i);  
  29.         else // 可以看出这里使用开放定址法来解决哈希冲突  
  30.             i = nextIndex(i, len);  
  31.         e = tab[i];  
  32.     }  
  33.     return null;  
  34. }  

(2)核心方法 set() 相关内容:
[java] view plain copy
 
 print?
  1. /** 
  2.  * 设置 key 的 value 
  3.  * 这里不使用 get() 方法中的快速路径,因为新建和更新的比例差不多, 
  4.  * 使用快速路径查找失败率很高 
  5.  */  
  6. private void set(ThreadLocal<?> key, Object value) {  
  7.   
  8.     Entry[] tab = table;  
  9.     int len = tab.length;  
  10.     int i = key.threadLocalHashCode & (len-1);  
  11.   
  12.     // 开发地址法,遍历查找  
  13.     for (Entry e = tab[i];  
  14.          e != null;  
  15.          e = tab[i = nextIndex(i, len)]) {  
  16.         ThreadLocal<?> k = e.get();  
  17.   
  18.         if (k == key) {  
  19.             e.value = value;  
  20.             return;  
  21.         }  
  22.   
  23.             // key 为 null,说明 ThreadLocal 实例已被回收,  
  24.             // 所以这里的 value 可以被覆盖。减少内存泄露的可能  
  25.         if (k == null) {  
  26.             replaceStaleEntry(key, value, i);  
  27.             return;  
  28.         }  
  29.     }  
  30.   
  31.     // 没有找到 Entry 则新建一个  
  32.     tab[i] = new Entry(key, value);  
  33.     int sz = ++size;  
  34.     if (!cleanSomeSlots(i, sz) && sz >= threshold)  
  35.         rehash();  
  36. }  
 
 
 
三. 参考资料
ThreadLocal JDK 1.8 源码

posted on 2017-07-05 10:55  alex5211314  阅读(172)  评论(0编辑  收藏  举报

导航