线程局部变量ThreadLocal实现原理
之前做项目用到过ThreadLocal,但是没有看源码层面的具体实现,今天特来补一补课。
ThreadLocal,即线程局部变量,用来为每一个使用它的线程维护一个独立的变量副本。
这种变量只在线程的生命周期内有效。并且与锁机制那种以时间换取空间的做法不同,ThreadLocal没有任何锁机制,它以空间换取时间的方式保证变量的线程安全。
1、ThreadLocal类图结构
SuppliedThreadLocal:主要是JDK1.8用来扩展对Lambda表达式的支持,有兴趣的自行百度。
ThreadLocalMap:是ThreadLocal的静态内部类,也是实际保存变量的类。
Entry:是ThreadLocalMap的静态内部类。ThreadLocalMap持有一个Entry数组,以ThreadLocal为key,变量为value,封装一个Entry。
2、Thread,ThreadLocal,ThreadLocalMap和Entry的关系
说明:
- 一个Thread拥有一个ThreadLocalMap对象;
- 一个ThreadLocalMap拥有多个Entry数组;
- 每个Entry都有k--v;
- Entry的key就是某个具体的ThreadLocal对象,是弱引用,key丢失后,GC不会回收Value会造成OOM(后面详细说);
3、ThreadLocal的set()方法
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); //1 if (map != null) map.set(this, value); //2 else createMap(t, value); }
可看出:
1、一个Thread只拥有一个ThreadLocalMap对象;
2、具体存值调用的是ThreadLocalMap的set(),传入的参数key就是当前ThreadLocal对象。
3.1、ThreadLocalMap的set()方法
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); // 1 for (Entry e = tab[i]; // 2 e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); // 3 int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) // 4 rehash(); }
可看出:
1、通过key的hashCode与数组容量 -1 取模,计算数组index;
2、从当前index开始遍历,清除key为null的无效Entry
3、将K-V封装为Entry,并放入数组
4、判断是否需要进行Entry数组扩容。threshold的值为数组容量的2/3。
大家可以回顾一下HashMap的源码,是不是很相似?
index都是和长度-1取模,也都是容量不够了,超出阈值就扩容(这里是扩容为2倍,和HashMap一样)。
3.2、扩容方法resize()
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(); if (k == null) { e.value = null; // Help the GC } else { int h = k.threadLocalHashCode & (newLen - 1); //1.计算新的索引 while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } setThreshold(newLen); size = count; table = newTab; }
1、然后遍历旧数组,根据新数组容量重新计算Entry在新数组中的位置。
4、ThreadLocal的get()方法
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); //ThreadLocalMap的getEntry()方法 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
4.1、ThreadLocalMap的getEntry()方法
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); // 1:计算索引 Entry e = table[i]; if (e != null && e.get() == key) // 2:当前Entry不为空,且key相同,直接返回entry; return e; else return getEntryAfterMiss(key, i, e); //3:否则去邻居index寻找 } private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { //4:循环查找,发现无效key就清楚,直到找到结束循环; ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }
5、ThreadLocal的remove()方法
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); //1:求得索引 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); //清除entry<k,v>,显示调用,可预防OOM expungeStaleEntry(i); return; } } }
注意:
static class Entry extends WeakReference<ThreadLocal<?>> {}
ThreadLocal可能存在OOM问题。
因为ThreadLocalMap是使用ThreadLocal的弱引用作为key的,发生GC时,key被回收,这样我们就无法访问key为null的value元素。
如果value本身是较大的对象,那么线程一直不结束的话,value就一直无法得到回收。特别是在我们使用线程池时,线程是复用的,不会杀死线程,这样ThreadLocal弱引用被回收时,value不会被回收。
所以,在使用ThreadLocal时,线程逻辑代码结束时,必须显示调用ThreadLocal.remove()方法。
参考致谢:
Over.......