ThreadLocal 线程本地变量,用于存储线程相关的数据,值存储在每个线程的持有的数据结构(ThreadLocalMap)当中,当在一个线程里边设置了ThreadLocal 变量的值,其他线程进行获取ThreadLocal变量值时是拜访不到

结构函数

十分简略,就供给了一个公有的结构函数

public class ThreadLocal<T> {
    public ThreadLocal() {
    }
}

可用于承继 ThreadLocal 覆写 initialValue 供给初始值

    protected T initialValue() {
        return null;
    } 

办法

  • ThreadLocal get() 办法,获取线程本地变量存储的值
    public T get() {
        // 获取当时线程
        Thread t = Thread.currentThread();
        // 获取线程存储的数据结构 ThreadLocalMap 
        ThreadLocalMap map = getMap(t);
        // 若 ThreadLocalMap 不为空
        if (map != null) {
           // 可从当时线程的 ThreadLocalMap 中获取之前set 过的值
           // 以 ThreadLocal<T> 为 key 
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 若当时线程的 ThreadLocalMap为空或未存储数据当时 ThreadLocal 的数据 
        return setInitialValue();
    }
      // ThreadLocalMap 归于线程的成员变量 
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
   private T setInitialValue() {
        // 调用 initialValue 获取初始值,默认为 null
        T value = initialValue();
        Thread t = Thread.currentThread();
        // 获取当时线程的 ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        // 假如当时线程现已创立过 ThreadLocalMap 直接赋值
        // 以当时线程本地变量 this 为 Key ,value 便是 ThreadLocal 存储的变量值
        if (map != null) {
            map.set(this, value);
        } else {
        // 若当时线程没有创立过 ThreadLocalMap 进行创立
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }
   // 1. 创立 ThreadLocalMap ,给它赋值给当时线程
   // 2. 创立 ThreadLocalMap 并把第一个 key value ,key 为 线程本地变量 this 放入Map 
   void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
  • ThreadLocal .set(T value) 办法 设置线程本地变量 ThreadLocal 的值
public void set(T value) {
    Thread t = Thread.currentThread();
    // 获取当时线程存储的 ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    // 若不为空,
    if (map != null) {
        // 以 this ThreadLocal<T> 为 key ,为当时 ThreadLocal变量赋值,存储在 ThreadLocalMap 中
        map.set(this, value); 
    } else {
    // 若为空,创立 ThreadLocalMap ,并把为当时 ThreadLocal 变量的值,存储在 ThreadLocalMap 中
        createMap(t, value);
    }
}
  • ThreadLocal .remove() 办法 ,从 ThreadLocalMap 移除数据
public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null) {
        m.remove(this);
    }
}

运用示例

public class ThreadLocalTest {
    public static void main(String[] args) {
        ThreadLocalTest test = new ThreadLocalTest();
        test.done();
    }
    private ThreadLocal<Integer> mThreadLocal = new ThreadLocal<>();
    private void done() {
        Integer value = mThreadLocal.get();
        // 当时主线程 mThreadLocal 未进行赋值,所认为空
        System.out.println(Thread.currentThread().getName() + " : " + value);
        // 当时主线程 mThreadLocal 赋值 为10010
        mThreadLocal.set(10010);
         // 获取到当时主线程 mThreadLocal 的值 为 10010
        value = mThreadLocal.get();
        System.out.println(Thread.currentThread().getName() + " : " + value);
        // 开启一个新线程
        final Thread thread = new Thread(() -> {
            // 子线程中进行获取 mThreadLocal ,
            // 虽然在主线程中mThreadLocal 现已赋值为 10010 ,可是子线程中是没有的
            Integer data = mThreadLocal.get();
            System.out.println(Thread.currentThread().getName() + " : " + data);
            // 给 mThreadLocal 变量赋值为 10086 
            mThreadLocal.set(10086);
            // 获取子线程中 mThreadLocal 变量的值
            data = mThreadLocal.get();
            System.out.println(Thread.currentThread().getName() + " : " + data);
        });
        // 启动子线程
        thread.start();
        try {
            // 等候子线程履行完结
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 子线程中虽然更改了 mThreadLocal 变量的值,可是 主线程中的 mThreadLocal 值依然是 10086 
        value = mThreadLocal.get();
        System.out.println(Thread.currentThread().getName() + " : " + value);
    }
}

输出

main : null
main : 10010
Thread-0 : null
Thread-0 : 10086
main : 10010

ThreadLocalMap 介绍

ThreadLocalMap 虽然不完成 Map 接口,可是能够把它看着以 ThreadLocal 为 key ,T 为 value 的 Map, ThreadLocalMap 归于一种 哈希 Map,不同于Java 集合结构中 的HashMap 。HashMap 运用哈希表来存储的,而且采用了链地址法处理冲突。 ThreadLocalMap 运用 斐波那契(Fibonacci)散列法 ,关于斐波那契数列 可参阅我的别的一篇文章 斐波那契数列与黄金分割比

public class ThreadLocal<T> {
    private final int threadLocalHashCode = nextHashCode();
    private static AtomicInteger nextHashCode =
        new AtomicInteger();
    /**
     * The difference between successively generated hash codes - turns
     * implicit sequential thread-local IDs into near-optimally spread
     * multiplicative hash values for power-of-two-sized tables.
     */
    private static final int HASH_INCREMENT = 0x61c88647;
    /**
     * Returns the next hash code.
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
 }

注意 threadLocalHashCode归于成员变量 ,nextHashCode 归于静态变量,每次创立 ThreadLocal 变量时结构函数初始化时会给 threadLocalHashCode 进行赋值为nextHashCode 加上 HASH_INCREMENT

HASH_INCREMENT 为

0x61c88647 = 1640531527 ≈ 2 ^ 32 * (1 – 1 / ) , = (√5 + 1) 2 为黄金分割比

运用 0x61c88647能让哈希码能均匀的散布在 2 的 N 次方的数组里。

结构函数

static class ThreadLocalMap {
    /**
       Entry 是弱引证,保存的 ThreadLocal<?> key, 
       当ThreadLocal<?> 没有被强引证引证时,会被收回,避免内存泄漏
     */
    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 createMap 中调用,创立进程中就存入了一个 ThreadLocal变量。
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
           // 创立一个 默认巨细为 16 ,2 的 4次方 Entry数组
            table = new Entry[INITIAL_CAPACITY]; 
            // 经过ThreadLocal 的hash值获取到 Entry 数组的索引值
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 
            table[i] = new Entry(firstKey, firstValue); // 把key value 存放在数组中
            size = 1; // ThreadLocalMap 存储 key,value 的巨细
            // 设置阈值 threshold = len * 2 / 3; 
            setThreshold(INITIAL_CAPACITY);
        }
}

办法

  • 给ThreadLocalMap 取值的进程

当经过 hash 核算出来的数组索引值中存储的 Key 不相一起,会找它下一个索引值的key进行判别是否持平,若下一个索引值 Entry 为空,则阐明ThreadLocalMap 未存储该 Key,回来 null


// ThreadLocal 取值时会把本身当成 key 从 ThreadLocalMap 取出
private Entry getEntry(ThreadLocal<?> key) {
    // 经过 ThreadLocal的hash值拿到索引值
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    // 经过索引值拿到 Entry ,当 e不为空,e存的key与当时取值的key相一起,回来之前设置的 value
    if (e != null && e.get() == key)
        return e;
    else
        // 1. 当 e 为空,之前没存过,
       // 2. 或者 发生了hash 碰撞,或许之前存储过 ThreadLocal,可是被收回,新生成的 ThreadLocal 和之前收回的 ThreadLocal 是同一个索引值,可是不持平
        return getEntryAfterMiss(key, i, e);
}
 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
     Entry[] tab = table;
     int len = tab.length;
    // 1. 当 e 为空,之前没存过,换岗循环之前回来 null 
    // 2. 遍历之 e 后边的索引存储的 ThreadLocal,若已被收回,进行整理,若和取值的key相同则回来Entry
     while (e != null) {
         // 若获取到key 相同,直接回来 Entry 
         ThreadLocal<?> k = e.get();
         if (k == key)
             return e;
         // 若 key 为空,阐明之前的ThreadLocal已被收回,可是 ThreadLocalMap 还未进行整理释放
         if (k == null)
             // 进行整理
             expungeStaleEntry(i);
         else
        // 获取Entry数组的下一个下一个索引 
             i = nextIndex(i, len); 
        // i 的下一个索引的 Entry 值赋值给 e,进行检查,若e的k值与key相同则回来,若为空则进行整理, 
        // Entry 数组没有存满,阈值时 size的三分之二 当碰到 Entry 为 null 时会跳出循环
         e = tab[i];
     }
     return null;
 }
// 整理当时staleSlot 索引的 Entry 
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    // 整理当时staleSlot索引的 Entry ,size 减 1
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;
    // Rehash until we encounter null
    Entry e;
    int i;
    // 遍历staleSlot下一个索引,当下一个索引对应的值为null时循环间断
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        // 当对应的索引的 Entry 的key 值为 null时,该 Entry 设置为空, size 减 1
        if (k == null) { 
            e.value = null;
            tab[i] = null;
            size--;
        } else {
        // 经过 k 的 hash 获取索引值,一般情况下 h 应该等于 i 
        // rehash 时,之前的索引有值时并不会覆盖原来的值,只会存储在下一个空 Entry 索引值里边
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                // 把 i 索引值设置为 null
                tab[i] = null; 
               // 若索引为 h 的 Entry 为空,正好 和 hash 索引值匹配上
               // 若不为空找,下一个为空的 Entry
                while (tab[h] != null) 
                    h = nextIndex(h, len);
                tab[h] = e; // 把索引为i的 Entry 值赋值给 tab[h] 
            }
        }
    }
    return i;
}
  • 给ThreadLocalMap 设置值的进程

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    // 经过 ThreadLocal 的hash值获取到索引
    int i = key.threadLocalHashCode & (len-1);
    // 遍历数组 e  = null 跳出循环
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        if (k == key) {
            e.value = value; // 当key持平时,进行赋值,回来
            return;
        }
        // 当 k 为 null 时,替换 Entry 
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    // 若当时索引位置为空,新建一个 Entry ,赋值给 tab[i] 
    tab[i] = new Entry(key, value);
    // 当时 size 加 1 
    int sz = ++size;
    // 若未整理出槽位而且,巨细大于等于阈值进行 rehash
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
 private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                int staleSlot) {
     Entry[] tab = table;
     int len = tab.length;
     Entry e;
    // slotToExpunge 代表预期需求铲除的索引值,初始化为staleSlot,遍历 staleSlot 上一个索引的 Entry,
    // 若上一个索引的 Entry 不为 null 而且该 Entry 的key被收回,把该索引值赋值给 slotToExpunge
    // 若上一个索引 Entry 为null 跳出循环
// 为了保存 staleSlot 索引值 不用被整理,找之前需求被整理的索引值
     int slotToExpunge = staleSlot;
     for (int i = prevIndex(staleSlot, len);
          (e = tab[i]) != null;
          i = prevIndex(i, len))
         if (e.get() == null)
             slotToExpunge = i;
     // 遍历 staleSlot 下一个索引的 Entry,
    // 若下一个索引 Entry 为null 跳出循环
     for (int i = nextIndex(staleSlot, len);
          (e = tab[i]) != null;
          i = nextIndex(i, len)) {
         ThreadLocal<?> k = e.get();
         // 若下一个索引的 Entry 的 key 和设置值的 key 持平 
         if (k == key) {
             e.value = value; // 进行赋值
             // 交换 i 和 staleSlot 的数据
             tab[i] = tab[staleSlot]; 
             tab[staleSlot] = e;
            // 假如需求铲除的索引值 slotToExpunge 等于 staleSlot 则slotToExpunge 赋值为 i,
    // 由于数据 i 和 staleSlot数据交换
             if (slotToExpunge == staleSlot)
                 slotToExpunge = i;
             // 铲除 key 已被收回的数据
             cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
             return;
         }
       // 若未找到持平的key,而且 k 为null,而且 slotToExpunge 等于 staleSlot
       // 需求整理的索引 slotToExpunge 赋值为 i
      // 为了保存 staleSlot 索引不被整理, 由于key的哈希值核算出的索引值正好为 staleSlot
         if (k == null && slotToExpunge == staleSlot)
             slotToExpunge = i;
     }
     // 若未找到持平的 key ,则创立新的 Entry 进行赋值
     tab[staleSlot].value = null;
     tab[staleSlot] = new Entry(key, value);
     // 假如需求铲除的索引值不等于 staleSlot 则进行铲除 key 已被收回的数据
    // 若等于则不需求铲除,由于已被赋值新 Entry
     if (slotToExpunge != staleSlot)
         cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
 }
 // 整理一下 Key 被收回的槽位   
 private boolean cleanSomeSlots(int i, int n) {
     boolean removed = false;
     Entry[] tab = table;
     int len = tab.length;
     do {
         i = nextIndex(i, len);
         Entry e = tab[i];
         if (e != null && e.get() == null) {
          // 当发现有需求铲除的 Entry 数据时,再次把 n 赋值为 数组长度
             n = len; 
             removed = true;
             i = expungeStaleEntry(i); //铲除索引为 i 的数据
         }
    // n 向右移动,时间复杂度为 logN ,为了加快时间,不进行全量整理
     } while ( (n >>>= 1) != 0);
     return removed;
 }    
 private void rehash() {
     // 在扩展数组巨细之前进行全量整理
     expungeStaleEntries();
     // 运用较低的阈值,避免迟滞
     if (size >= threshold - threshold / 4)
         resize();
 }
  //  遍历数组铲除一切被收回的 key 的 Entry
 private void expungeStaleEntries() {
     Entry[] tab = table;
     int len = tab.length;
     for (int j = 0; j < len; j++) {
         Entry e = tab[j];
         if (e != null && e.get() == null)
             expungeStaleEntry(j);
     }
 }
// 数组容量翻倍                             
 private void resize() {
     Entry[] oldTab = table;
     int oldLen = oldTab.length;
     int newLen = oldLen * 2;
     // 进建一个之前巨细2倍的数组                        
     Entry[] newTab = new Entry[newLen];
     int count = 0;
      // 对原数组进行拷贝
     for (int j = 0; j < oldLen; ++j) {
         Entry e = oldTab[j];
         if (e != null) {
             ThreadLocal<?> k = e.get();
             // 假如之前的key为空,阐明已被收回,不进行拷贝直接略过
             if (k == null) {
                 e.value = null; // Help the GC
             } else {
                // 重新核算 hash 值 
                 int h = k.threadLocalHashCode & (newLen - 1);
                 // 最好这个核算出的hash索引值为空,假如已被占用找到一个不为空的槽位
                 while (newTab[h] != null)
                     h = nextIndex(h, newLen);
                // 把旧的值赋值到新数组
                 newTab[h] = e;
                 count++;
             }
         }
     }
      // 设置新的阈值,更新size 巨细,更新数组
     setThreshold(newLen);
     size = count;
     table = newTab;
 }                             
  • ThreadLocalMap 移除数据十分简略,先用 hash 值找到索引,若和 key 持平直接移除,若不持平查找下一个不为null的索引,判别是否等于key进行移除.
private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}