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的完成道理的剖析引见(附代码)的细致内容,更多请关注ki4网别的相干文章!