Junhc

岂止于博客

死磕Java并发之ThreadLocal

public class ThreadLocal<T> {

  private final int threadLocalHashCode = nextHashCode();
  private static AtomicInteger nextHashCode = new AtomicInteger();
  private static final int HASH_INCREMENT = 0x61c88647;
  private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
  }

  public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
  }

  private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
  }

  public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
  }

  ThreadLocalMap getMap(Thread t) {
    // 实际上就是访问Thread类中的ThreadLocalMap这个成员变量
    // 每一个线程都有自己单独的ThreadLocalMap实例,而对应这个线程的所有本地变量都会保存到这个map内
    return t.threadLocals;
  }

  void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
  }
  ...
}
public
class Thread implements Runnable {
  /* ThreadLocal values pertaining to this thread. This map is maintained
   * by the ThreadLocal class. */
  ThreadLocal.ThreadLocalMap threadLocals = null;  
  ...
}
static class ThreadLocalMap {
  // 使用弱引用的对象,不会阻止它所指向的对象被垃圾回收器回收
  static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);

        value = v;
    }
  }

  ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    //构造一个Entry数组,并设置初始大小
    table = new Entry[INITIAL_CAPACITY];
    //计算Entry数据下标
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    //将`firstValue`存入到指定的table下标中
    table[i] = new Entry(firstKey, firstValue);
    //设置节点长度为1
    size = 1;
    //设置扩容的阈值
    setThreshold(INITIAL_CAPACITY);
  }

  private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    // 根据哈希码和数组长度求元素放置的位置,即数组下标
    int i = key.threadLocalHashCode & (len-1);
    // 从i开始往后一直遍历到数组最后一个Entry(线性探索)
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        // 如果key相等,覆盖value
        if (k == key) {
            e.value = value;
            return;
        }
        // 如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    // 如果超过阀值,就需要扩容了
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
  }
  ...
}
魔数0x61c88647

魔数0x61c88647的选取和斐波那契散列有关,0x61c88647对应的十进制为1640531527。
而斐波那契散列(f(n) = f(n-1) + f(n-2) (n>1))的乘数可以用 (long)((1L<<31)*(Math.sqrt(5)-1))
如果把这个值给转为带符号的int,则会得到1640531527。

private static final int HASH_INCREMENT = 0x61c88647;

private static void magicHash(int size) {
  int hashCode = 0;
  for (int i = 0; i < size; i++) {
      hashCode = i * HASH_INCREMENT + HASH_INCREMENT;
      // 我们用0x61c88647作为魔数累加为每个ThreadLocal分配各自的ID也就是threadLocalHashCode再与2的幂取模,得到的结果分布很均匀
      System.out.printf((hashCode & (size - 1)) + " ");
  }
  System.out.println();
}

public static void main(String[] args) {
  magicHash(16);
  magicHash(32);
  // 7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0
  // 7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0
}
ThreadLocal的内存泄漏

ThreadLocalMap中Entry的key使用的是ThreadLocal的弱引用,
如果一个ThreadLocal没有外部强引用,当系统执行GC时,这个ThreadLocal势必会被回收,
这样一来,ThreadLocalMap中就会出现一个key为null的Entry,
而这个key=null的Entry是无法访问的,当这个线程一直没有结束的话,那么就会存在一条强引用链

参考资料