ThreadLocal万字总结https://blog.csdn.net/sinat_33921105/article/details/103295070
key的唯一性
一个线程中的多个ThreadLocal变量如何存储、如何保证唯一性?
每一个 ThreadLocal<T> tl = new ThreadLocal<>(); 创建出来都有一个不变且唯一的threadLocalHashCode,这个threadLocalHashCode在本线程局部变量的存储中作为唯一识别标志参与到key-value存储的key的计算,非常重要!
public class ThreadLocal<T> {private final int threadLocalHashCode = nextHashCode();private static AtomicInteger nextHashCode = new AtomicInteger();private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);}public ThreadLocal() {}
}
线程独有
从源码的角度看,为什么ThreadLocal变量是线程独有,不同线程之间不会互相干扰,降低编程复杂性和从根源上避免线程安全问题。
ThreadLocal.set(value)取得是当前线程的ThreadLocalMap,如果存在设置值,如果不存在创建。
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}
取当前线程的ThreadLocalMap:
ThreadLocalMap getMap(Thread t) {return t.threadLocals;}
当前线程中ThreadLocalMap的定义:
public class Thread implements Runnable { ThreadLocal.ThreadLocalMap threadLocals = null;
}
当前线程中ThreadLocalMap如果不存在创建:
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}
set方法原理
以当前变量为key存储在当前Thread.ThreadLocalMap中的键值对。key的唯一性通过threadLocalHashCode与容量的计算来保证。
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
作为下标存储在数组中,这是threadLocalHashCode唯一性重要性的体现,与数组的容量取余操作保证下标不越界(如果容量不足会有其他方法进行扩容)
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;
//阈值,容量超过threshold的3/4会扩容,threshold = INITIAL_CAPACITY * 2/3;setThreshold(INITIAL_CAPACITY);}
ThreadLocalMap.set:当ThreadLocal变量中存在Map时会直接调用Map的set方法,
private void set(ThreadLocal<?> key, Object value) {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();//如果已存在该变量的值,覆盖if (k == key) {e.value = value;return;}//set方法会清除泄露的值if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;//容量大于阈值,扩容需要重新散列存储if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}
get方法原理
知道了set的存储机制,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) {//依然通过threadLocalHashCode计算获取下标int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;else//如果取不到值,通过其他方式获得return getEntryAfterMiss(key, i, e);}
remove方法
建议大家使用最后都主动调用ThreadLocal.remove()方法,防止内存泄露,虽然ThreadLocal是弱引用,每次GC都会回收,set在ThreadLocal里的数据并没有被同时清除。详细了解可以看文章开头引用的文章。
Java的四种引用类型
强引用:我们常常 new 出来的对象就是强引用类型,只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足的时候
软引用:使用 SoftReference 修饰的对象被称为软引用,软引用指向的对象在内存要溢出的时候被回收
弱引用:使用 WeakReference 修饰的对象被称为弱引用,只要发生垃圾回收,若这个对象只被弱引用指向,那么就会被回收
虚引用:虚引用是最弱的引用,在 Java 中使用 PhantomReference 进行定义。虚引用中唯一的作用就是用队列接收对象即将死亡的通知
以下是源码其他方法上文未作出解释的,如果需要自行查看。
public class ThreadLocal<T> {protected T initialValue() {return null;}public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {return new SuppliedThreadLocal<>(supplier);}public ThreadLocal() {}private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);}T childValue(T parentValue) {throw new UnsupportedOperationException();}static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {private final Supplier<? extends T> supplier;SuppliedThreadLocal(Supplier<? extends T> supplier) {this.supplier = Objects.requireNonNull(supplier);}@Overrideprotected T initialValue() {return supplier.get();}}static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}private static final int INITIAL_CAPACITY = 16;private Entry[] table;private int size = 0;private int threshold; // Default to 0private void setThreshold(int len) {threshold = len * 2 / 3;}private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1);}private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);table = new Entry[len];for (int j = 0; j < len; j++) {Entry e = parentTable[j];if (e != null) {@SuppressWarnings("unchecked")ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();if (key != null) {Object value = key.childValue(e.value);Entry c = new Entry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)h = nextIndex(h, len);table[h] = c;size++;}}}}private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {Entry[] tab = table;int len = tab.length;while (e != null) {ThreadLocal<?> k = e.get();if (k == key)return e;if (k == null)expungeStaleEntry(i);elsei = nextIndex(i, len);e = tab[i];}return null;}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;}}}private void replaceStaleEntry(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;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 firstfor (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 existsif (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;}// If key not found, put new entry in stale slottab[staleSlot].value = null;tab[staleSlot] = new Entry(key, value);// If there are any other stale entries in run, expunge themif (slotToExpunge != staleSlot)cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);}private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;// expunge entry at staleSlottab[staleSlot].value = null;tab[staleSlot] = null;size--;// Rehash until we encounter nullEntry 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;}private boolean cleanSomeSlots(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;}private void rehash() {expungeStaleEntries();// Use lower threshold for doubling to avoid hysteresisif (size >= threshold - threshold / 4)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;}private void expungeStaleEntries() {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);}}}
}