根据ThreadLocal的定义,很容易想到的实现方法是在ThreadLocal内部定义一个Map,Key保存线程,Value保存值。
但是JDK并不是这样完成的,反而是在每个Thread中保存了ThreadLocalMap的引用,而一个ThreadLocalMap中保存着一组Entry实例,Entry实例内部即是一个ThreadLocal和他所存储的Value值。
static class ThreadLocalMap { private Entry[] table; private int size = 0; private int threshold; ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
初始化的方法和HashMap有异曲同工之妙,具体的方法操作我们通过一般使用ThreadLocal的方法来看一看。
首先我们一般会重写一个initValue()
protected T initialValue() { return null; }
不出意料方法体内为空,而JDK注释里指出
This method will be invoked the first
time a thread accesses the variable with the {@link #get}
method, unless the thread previously invoked the {@link #set}
method, in which case the {@code initialValue} method will not
be invoked for the thread
意思是这个方法只会在某个线程第一次通过get方法访问变量时调用。除非先行调用了set,此时这个方法不会被调用。
那我们接下来看看set方法和get方法
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t);//从当前线程中获得ThreadLocalMap if (map != null)//map不为空时直接设值 map.set(this, value); else//为空时,初始化创建一个map(懒加载) createMap(t, value); } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
1 private void set(ThreadLocal<?> key, Object value) { 2 Entry[] tab = table; 3 int len = tab.length; 4 int i = key.threadLocalHashCode & (len-1);//根据hashcode获得在数组的索引 5 6 //遍历桶(开发地址法) 7 for (Entry e = tab[i]; 8 e != null; 9 e = tab[i = nextIndex(i, len)]) { 10 //获取ThreadLocal 11 ThreadLocal<?> k = e.get(); 12 13 //是所需要的Key,设值返回 14 if (k == key) { 15 e.value = value; 16 return; 17 } 18 19 //过期(被回收),替代过期值返回 20 if (k == null) { 21 replaceStaleEntry(key, value, i); 22 return; 23 } 24 } 25 26 //获得索引值开始第一个非null的i,新建Entry存入 27 tab[i] = new Entry(key, value); 28 //如果超过threshold扩容(参照HashMap) 29 int sz = ++size; 30 if (!cleanSomeSlots(i, sz) && sz >= threshold) 31 rehash(); 32 }
1 public T get() { 2 Thread t = Thread.currentThread(); 3 ThreadLocalMap map = getMap(t);//从当前线程中获得ThreadLocalMap 4 if (map != null) {//map不为空时 5 ThreadLocalMap.Entry e = map.getEntry(this);//从Map中根据Key(ThreadLocal)找到对应的Entry 6 if (e != null) {//找到(Entry不为空) 7 @SuppressWarnings("unchecked") 8 T result = (T)e.value;//值就是Value 9 return result;//返回 10 } 11 } 12 return setInitialValue();//map为空 或者 map不为空但没有当前ThreadLocal所存的值(可能存在其他ThreadLocal) 13 //设置当前ThreadLocal的初始值 14 }
1 private Entry getEntry(ThreadLocal<?> key) { 2 int i = key.threadLocalHashCode & (table.length - 1); 3 Entry e = table[i];//根据hashcode获得在数组的索引 4 if (e != null && e.get() == key)//找到(Entry不为空),直接返回 5 return e; 6 else//没有找到(没初始化或哈希冲突) 7 return getEntryAfterMiss(key, i, e); 8 } 9 10 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { 11 Entry[] tab = table; 12 int len = tab.length; 13 14 //遍历桶(开发地址法) 15 while (e != null) { 16 ThreadLocal<?> k = e.get(); 17 if (k == key)//找到,直接返回 18 return e; 19 if (k == null)//发现存在过期的,清理 20 expungeStaleEntry(i); 21 else 22 i = nextIndex(i, len); 23 e = tab[i]; 24 } 25 //遍历到第一个为null的都没找到,即桶内没有该元素,返回null 26 return null; 27 }
我们可以在以上方法中看到,当访问ThreadLocalMap中的Key(ThreadLocal)为空的时候,
getEntry会调用expungeStaleEntry(int staleSlot),set会调用replaceStaleEntry(ThreadLocal<?> key, Object value,int staleSlot),
这两个方法都是清理空Key(null)的value值的。
是因为ThreadLocal本身存值是存放在Entry中的,当ThreadLocal被程序员主动释放(null)的时候,ThreadLocal在Map中的软引用也立刻被释放,
但是其对应的Value在Map中仍被引用,无法被释放。
而因为是用的开放地址法处理哈希冲突,删除对应值的时候需要重新计算后续元素的哈希值,并也会发现其他没被清理的值。
1 private int expungeStaleEntry(int staleSlot) { 2 Entry[] tab = table; 3 int len = tab.length; 4 5 // 清除指明的点的value 6 tab[staleSlot].value = null; 7 tab[staleSlot] = null; 8 size--; 9 10 // 重新计算哈希 11 Entry e; 12 int i; 13 for (i = nextIndex(staleSlot, len); 14 (e = tab[i]) != null;//一直到第一个空节点 15 i = nextIndex(i, len)) { 16 ThreadLocal<?> k = e.get(); 17 if (k == null) {//key为空而entry不为空,需要清除 18 e.value = null; 19 tab[i] = null; 20 size--; 21 } else {//key不为空,重新计算哈希值 22 int h = k.threadLocalHashCode & (len - 1); 23 if (h != i) { 24 tab[i] = null; 25 26 while (tab[h] != null)//开放地址法 27 h = nextIndex(h, len); 28 tab[h] = e; 29 } 30 } 31 } 32 return i; 33 }