This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID)
publicclassMain{ privatestaticfinal ThreadLocal<String> LOCAL = new ThreadLocal<>(); publicstaticvoidmain(String[] args)throws Exception{ LOCAL.set("doge"); System.out.println(LOCAL.get()); } }
线程实例和ThreadLocal实例的关系如下:
上面只描述了单线程的情况并且因为是主线程忽略了Thread t = new Thread()这一步,如果有多个线程会稍微复杂一些,但是原理是不变的,ThreadLocal实例总是通过Thread.currentThread()获取到当前操作线程实例,然后去操作线程实例中的ThreadLocalMap类型的成员变量,因此它是一个桥梁,本身不具备存储功能。
// 如果Key在哈希表中找不到哈希槽的时候会调用此方法 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e){ Entry[] tab = table; int len = tab.length; // 这里会通过nextIndex尝试遍历整个哈希表,如果找到匹配的Key则返回Entry // 如果哈希表中存在Key == null的情况,调用expungeStaleEntry进行清理 while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } returnnull; }
// 1.清空staleSlot对应哈希槽的Key和Value // 2.对staleSlot到下一个空的哈希槽之间的所有可能冲突的哈希表部分槽进行重哈希,置空Key为null的槽 // 3.注意返回值是staleSlot之后的下一个空的哈希槽的哈希码 privateintexpungeStaleEntry(int staleSlot){ Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot // 清空staleSlot对应哈希槽的Key和Value tab[staleSlot].value = null; tab[staleSlot] = null; size--;
// Rehash until we encounter null // 下面的过程是对staleSlot到下一个空的哈希槽之间的所有可能冲突的哈希表部分槽进行重哈希,置空Key为null的槽 Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; tab[i] = null; size--; } else { int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until // null because multiple entries could have been stale. while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; }
// 这里个方法比较长,作用是替换哈希码为staleSlot的哈希槽中Entry的值 privatevoidreplaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot){ Entry[] tab = table; int len = tab.length; Entry e;
// Back up to check for prior stale entry in current run. // We clean out whole runs at a time to avoid continual // incremental rehashing due to garbage collector freeing // up refs in bunches (i.e., whenever the collector runs). int slotToExpunge = staleSlot; // 这个循环主要是为了找到staleSlot之前的最前面的一个Key为null的哈希槽的哈希码 for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i;
// Find either the key or trailing null slot of run, whichever // occurs first // 遍历staleSlot之后的哈希槽,如果Key匹配则用输入值替换 for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get();
// If we find key, then we need to swap it // with the stale entry to maintain hash table order. // The newly stale slot, or any other stale slot // encountered above it, can then be sent to expungeStaleEntry // to remove or rehash all of the other entries in run. if (k == key) { e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e; // Start expunge at preceding stale entry if it exists if (slotToExpunge == staleSlot) slotToExpunge = i; cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; }
// If we didn't find stale entry on backward scan, the // first stale entry seen while scanning for key is the // first still present in the run. if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } // Key匹配不了,则新创建一个哈希槽 // If key not found, put new entry in stale slot tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // 这里如果当前的staleSlot和找到前置的slotToExpunge不一致会进行一次清理 // If there are any other stale entries in run, expunge them if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }
// 对当前哈希表中所有的Key为null的Entry调用expungeStaleEntry privatevoidexpungeStaleEntries(){ Entry[] tab = table; int len = tab.length; for (int j = 0; j < len; j++) { Entry e = tab[j]; if (e != null && e.get() == null) expungeStaleEntry(j); } }
// 清理第i个哈希槽之后的n个哈希槽,如果遍历的时候发现Entry的Key为null,则n会重置为哈希表的长度,expungeStaleEntry有可能会重哈希使得哈希表长度发生变化 privatebooleancleanSomeSlots(int i, int n){ boolean removed = false; Entry[] tab = table; int len = tab.length; do { i = nextIndex(i, len); Entry e = tab[i]; if (e != null && e.get() == null) { n = len; removed = true; i = expungeStaleEntry(i); } } while ( (n >>>= 1) != 0); return removed; }
/** * 这个方法主要给`ThreadLocal#get()`调用,通过当前ThreadLocal实例获取哈希表中对应的Entry * */ private Entry getEntry(ThreadLocal<?> key){ // 计算Entry的哈希值 int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else// 注意这里,如果e为null或者Key对不上,会调用getEntryAfterMiss return getEntryAfterMiss(key, i, e); }
// Use lower threshold for doubling to avoid hysteresis // 哈希表的哈希元素个数大于3/4阈值时候触发扩容 if (size >= threshold - threshold / 4) resize(); }
// 扩容,简单的扩大2倍的容量 privatevoidresize(){ Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0;
for (Entry e : oldTab) { 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++; } } }
// We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not.
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)]) { ThreadLocal<?> k = e.get(); // Key匹配,直接设置值 if (k == key) { e.value = value; return; } // 如果Entry的Key为null,则替换该Key为当前的key,并且设置值 if (k == null) { replaceStaleEntry(key, value, i); return; } }
tab[i] = new Entry(key, value); int sz = ++size; // 清理当前新设置元素的哈希槽下标到sz段的哈希槽,如果清理成功并且sz大于阈值则触发扩容 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
public T get(){ //获取当前线程的实例 Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { //根据当前的ThreadLocal实例获取ThreadLocalMap中的Entry,使用的是ThreadLocalMap的getEntry方法 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T) e.value; return result; } } //线程实例中的threadLocals为null,则调用initialValue方法,并且创建ThreadLocalMap赋值到threadLocals return setInitialValue(); }
private T setInitialValue(){ // 调用initialValue方法获取值 T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // ThreadLocalMap如果未初始化则进行一次创建,已初始化则直接设置值 if (map != null) map.set(this, value); else createMap(t, value); return value; }
其实ThreadLocal在设置内部类ThreadLocal.ThreadLocalMap中构建的Entry哈希表已经考虑到内存泄漏的问题,所以ThreadLocal.ThreadLocalMap$Entry类设计为弱引用,类签名为static class Entry extends WeakReference<ThreadLocal<?>>。之前一篇文章介绍过,如果弱引用关联的对象如果置为null,那么该弱引用会在下一次GC时候回收弱引用关联的对象。举个例子:
1 2 3 4 5 6 7 8 9 10 11
publicclassThreadLocalMain{
privatestatic ThreadLocal<Integer> TL_1 = new ThreadLocal<>();