线程局部变量ThreadLocal实现原理
Posted sunshine-ground-poems
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程局部变量ThreadLocal实现原理相关的知识,希望对你有一定的参考价值。
ThreadLocal,即线程局部变量,用来为每一个使用它的线程维护一个独立的变量副本。这种变量只在线程的生命周期内有效。并且与锁机制那种以时间换取空间的做法不同,ThreadLocal没有任何锁机制,它以空间换取时间的方式保证变量的线程安全。
本篇从源码方面分析ThreadLocal的实现原理。
先看一下ThreadLocal类图结构
SuppliedThreadLocal主要是JDK1.8用来扩展对Lambda表达式的支持,有兴趣的自行百度。
ThreadLocalMap是ThreadLocal的静态内部类,也是实际保存变量的类。
Entry是ThreadLocalMap的静态内部类。ThreadLocalMap持有一个Entry数组,以ThreadLocal为key,变量为value,封装一个Entry。
下面以一张图简要说明Thread,ThreadLocal,ThreadLocalMap和Entry的关系。
说明一下上图:
- 一个Thread拥有一个ThreadLocalMap对象
- ThreadLocalMap拥有一个Entry数组
- 每个Entry都有k--v
- Entry的key就是某个具体的ThreadLocal对象
下面分析主要方法。
1、set()
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
这里可以看出:一个Thread只拥有一个ThreadLocalMap对象;具体存值调用的是ThreadLocalMap的set(),传入的参数key就是当前ThreadLocal对象。
再看看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(); }
- 通过key的hashCode与数组容量 -1 取模,计算数组index
- 从当前index开始遍历,清除key为null的无效Entry
- 将K-V封装为Entry,并放入数组
- 判断是否需要进行Entry数组扩容。threshold的值为数组容量的2/3。
看看扩容的resize()方法:
private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 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); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } setThreshold(newLen); size = count; table = newTab; }
这里主要就是扩容为原先的2倍。然后遍历旧数组,根据新数组容量重新计算Entry在新数组中的位置。
2、get()
ThreadLocal的get()方法如下:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
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 return e; else return getEntryAfterMiss(key, i, e); //3 } private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { //4 ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }
- 计算index
- 当前index上的Entry不为空且key相同,直接返回
- 否则去相邻index寻找
- 循环查找,发现无效key就清除。找到就结束循环。
3、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); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }
处理方式和查找保存类似,删除对应Entry后都会去除key为null的无效元素。
注意
static class Entry extends WeakReference<ThreadLocal<?>> {}
ThreadLocal可能存在OOM问题。因为ThreadLocalMap是使用ThreadLocal的弱引用作为key的,发生GC时,key被回收,这样我们就无法访问key为null的value元素,如果value本身是较大的对象,那么线程一直不结束的话,value就一直无法得到回收。特别是在我们使用线程池时,线程是复用的,不会杀死线程,这样ThreadLocal弱引用被回收时,value不会被回收。
在使用ThreadLocal时,线程逻辑代码结束时,必须显示调用ThreadLocal.remove()方法。
以上是关于线程局部变量ThreadLocal实现原理的主要内容,如果未能解决你的问题,请参考以下文章