ThreadLocal是一個關於建立線程局部變量的類,這個變量只能當前線程使用,其餘線程不可用。
ThreadLocal提供get()和set()方法建立和修改變量。java
ThreadLocal threadLocal = new ThreadLocal();
ThreadLocal<String> threadLocal = new ThreadLocal<>();
ThreadLocal threadLocal = new ThreadLocal<String>() { @Override protected String initialValue() { return "初始化值"; } };
查看ThreadLocal中的get(),set()中有一個ThreadLocalMap對象數組
//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); } //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 就是一個內部靜態類,沒有繼承也沒有接口,是一個自定義的Hash映射,用戶維護線程局部變量。數據結構
static class ThreadLocalMap
static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { //key放在WeakReference<ThreadLocal<?>>中 super(k); //變量放在Object value中 value = v; } }
private Entry[] table;
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { //默認容量爲16 table = new Entry[INITIAL_CAPACITY]; //threadLocalHashCode是一個原子類AtomicInteger的實例,每次調用會增長0x61c88647。&位移操做使存放分佈均勻 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); //放入數組 table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } //nextHashCode實現 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); }
private Entry getEntry(ThreadLocal<?> key) { //定位i的位置 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); } private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; //hashcode索引相同因此查找下一個,用循環比對取出 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]; } return null; }
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; //hashcode索引 int i = key.threadLocalHashCode & (len-1); //線性探測法,若是在有值的狀況下,key不一樣則繼續下一個 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //若是當前有值&&key相同則更新value if (k == key) { e.value = value; return; } //若是key空,則key-value從新替換 if (k == null) { replaceStaleEntry(key, value, i); return; } } //索引位置找到,插入key-value,對size+1 tab[i] = new Entry(key, value); int sz = ++size; //cleanSomeSlots清理key關聯的對象被回收的數據,若是沒有被清理的&&size大於擴容因子,刷新 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
1.計算索引位置
2.若是當前位置有值則索引+1判斷是否爲空,不爲空繼續+1,直到找到位置插入
3.size+1
4.是否清理key爲null的數據,若是沒有被清理&& size大於列表長度的2/3則擴容ide
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]; //key爲null,被清理 if (e != null && e.get() == null) { n = len; removed = true; //移除i位置以後爲key爲null的元素 i = expungeStaleEntry(i); } } while ( (n >>>= 1) != 0); return removed; }
private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; //將上面staleSlot的數據清空,大小減去1 tab[staleSlot].value = null; tab[staleSlot] = null; size--; Entry e; int i; //以staleSlot日後找key爲null的 for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); //key爲null清空 if (k == null) { e.value = null; tab[i] = null; size--; } else { //key不爲null,計算當前hashCode索引位置,若是不相同則把當前i清除,當前h位置不爲null,再向後查找key合適的索引 int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null; while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; }
private void rehash() { expungeStaleEntries(); //調用expungeStaleEntries()方法 //size的長度超過容量的3/4,則擴容 if (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(); //key爲null,value也設置爲null,清理 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); } }
從Entry源碼中能夠看出,Entry繼承了WeakReference弱引用,若是外部沒有引用ThreadLocal,則Entry中做爲Key的ThreadLocal會被銷燬成爲null,那麼它所對應的value不會被訪問到。當線程一直在執行&&沒有進行remove,rehash等操做時,value會一直存在內存,從而形成內存泄露this