public void set(T value) { // 获取当前线程 Thread t = Thread.currentThread(); // 获取当前线程的ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
通过此方法我们可以看到ThreadLocalMap是被每个线程独立拥有的,每个线程持有一个map的引用,这也是ThreadLocal实现线程隔离的思想
这样就把要保存数据的生命周期交给了thread自己管理,thread销毁的同时,保存的数据也一起跟着被销毁
class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } private static final int INITIAL_CAPACITY = 16; private Entry[] table; private int size = 0; private int threshold; }
可以看出这并不是我们平常所看到的map,是通过table数组保存entry对象,同时每个entry对象保存相应的key-value
思考以下问题:
带着问题的开始寻找答案
这里使用了线性探测法解决hash冲突
看threadLocalMap的set方法:
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; // 通过位运算计算数组下标,位运算保证i的值小于等于len-1 int i = key.threadLocalHashCode & (len-1); /** * 插入的时候发生了hash冲突 */ for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); /** * 说明key已经存在了,直接覆盖就行了 */ if (k == key) { e.value = value; return; } /** * 数组元素不等于null,但是key等于null,说明ThreadLocal弱引用已经被回收 * 但是value依然存在,需要释放该元素同时插入新的元素 */ if (k == null) { replaceStaleEntry(key, value, i); return; } // 如果上面两个条件都不满足 // 继续寻找下一个null的槽位,i的值一直在变化 } /** * 没有产生hash冲突的话,就直接在该数组位置插入 */ tab[i] = new Entry(key, value); int sz = ++size; // 双重判断 // 1 首先判断是否有可以被回收的节点 // 2 判断size是否超过了临界值 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
我们先看map删除一个元素都做了什么操作,然后一步步分析
private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; //1 expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size--; // Rehash until we encounter null Entry e; int i; //2 开始整理工作 for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); /** 3 * 开始清理工作,说明这个threadlocal已经被gc回收了 * 但是value作为强引用依然没有被回收,这里需要进行一个清理工作 */ if (k == null) { e.value = null; tab[i] = null; size--; } else { int h = k.threadLocalHashCode & (len - 1); //4 把e放到它正真应该存在的位置去 if (h != i) { // 释放当前槽位 tab[i] = null; // Unlike Knuth 6.4 Algorithm R, we must scan until // null because multiple entries could have been stale. // 发现本属于自己位置还是依然被占用着 // 继续寻找插入e的位置(用开放寻址法来解决散列冲突) // 5 while (tab[h] != null) h = nextIndex(h, len); // 6 tab[h] = e; } } } return i; }