ThreadLocal源码探析( 四 )

HashCode 计算ThreaLocalMap中没有采用传统的调用ThreadLocal的hashcode方法(继承自object的hashcode) , 而是调用nexthashcode , 源码如下:
private final int threadLocalHashCode = nextHashCode();private static AtomicInteger nextHashCode = new AtomicInteger(); //1640531527 能够让hash槽位分布相当均匀private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() {      return nextHashCode.getAndAdd(HASH_INCREMENT);}Hash冲突和HashMap的最大的不同在于 , ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1及线性探测 , 寻找下一个相邻的位置 。
/** * Increment i modulo len. */private static int nextIndex(int i, int len) {    return ((i + 1 < len) ? i + 1 : 0);}/** * Decrement i modulo len. */private static int prevIndex(int i, int len) {    return ((i - 1 >= 0) ? i - 1 : len - 1);}ThreadLocalMap采用线性探测的方式解决Hash冲突的效率很低 , 如有大量不同的ThreadLocal对象放入map中时发生冲突 。所以建议每个线程只存一个变量(一个ThreadLocal)就不存在Hash冲突的问题 , 如果一个线程要保存set多个变量 , 就需要创建多个ThreadLocal , 多个ThreadLocal放入Map中时会极大的增加Hash冲突的可能 。
清楚意思吗?当你在一个线程需要保存多个变量时 , 你以为是多次set?你错了你得创建多个ThreadLocal , 多次set的达不到存储多个变量的目的 。
sThreadLocal.set("这是在线程a中");Key的弱引用问题看看官话 , 为什么要用弱引用 。

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了处理非常大和生命周期非常长的线程 , 哈希表使用弱引用作为 key 。
  • 生命周期长:暂时可以想到线程池中的线程
ThreadLocal在没有外部对象强引用时如Thread , 发生GC时弱引用Key会被回收 , 而Value是强引用不会回收 , 如果创建ThreadLocal的线程一直持续运行如线程池中的线程 , 那么这个Entry对象中的value就有可能一直得不到回收 , 发生内存泄露 。
  • key 如果使用强引用:引用的ThreadLocal的对象被回收了 , 但是ThreadLocalMap还持有ThreadLocal的强引用 , 如果没有手动删除 , ThreadLocal不会被回收 , 导致Entry内存泄漏 。
  • key 使用弱引用:引用的ThreadLocal的对象被回收了 , 由于ThreadLocalMap持有ThreadLocal的弱引用 , 即使没有手动删除 , ThreadLocal也会被回收 。value在下一次ThreadLocalMap调用set,get , remove的时候会被清除 。
Java8中已经做了一些优化如 , 在ThreadLocal的get()、set()、remove()方法调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value , 并将整个Entry设置为null , 利于下次内存回收 。
Java8中for循环遍历整个Entry数组 , 遇到key=null的就会替换从而避免内存泄露的问题 。
       private int expungeStaleEntry(int staleSlot) {            Entry[] tab = table;            int len = tab.length;            // expunge entry at staleSlot            tab[staleSlot].value = null;            tab[staleSlot] = null;            size--;            // Rehash until we encounter null            Entry e;            int i;            for (i = nextIndex(staleSlot, len);                 (e = tab[i]) != null;                 i = nextIndex(i, len)) {                ThreadLocal<?> k = e.get();                if (k == null) {                    e.value = null;                    tab[i] = null;                    size--;                } else {                    int h = k.threadLocalHashCode & (len - 1);                    if (h != i) {                       tab[i] = null;                        while (tab[h] != null)                            h = nextIndex(h, len);                        tab[h] = e;                    }                }            }            return i;        }


推荐阅读