一、ThreadLocal的實現
ThreadLocal源碼:
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);
}
protected T initialValue() {
return null;
}
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
public ThreadLocal() {
}
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();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
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);
}
@Override
protected T initialValue() {
return supplier.get();
}
}
}
上面的代碼先不用看,跟着分析的思路走,核心的一些東西都會解釋到的,先看下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);
}
可以看出,我們需要的線程變量被維護在一個ThreadLocalMap對象裏。而ThreadLocalMap維護在Thread裏面。
下面看下ThreadLocalMap這個類,這是ThreadLocal的靜態內部類,應該是希望不讓ThreadLocalMap與其他不相關的類產生關係,所以使用靜態內部類的方式來定義。
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 0
private 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);
}
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;
setThreshold(INITIAL_CAPACITY);
}
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 getEntry(ThreadLocal<?> key) {
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;
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;
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;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
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;
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
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;
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();
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();
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);
}
}
}
ThreadLocalMap使用Entry保存值,看下Entry結構
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
//實際上真正弱引用的是ThreadLocal對象
super(k);
value = v;
}
}
Entry繼承了WeakReference。
看下ThreadLocalMap的構造方法:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//創建一個容量爲16的Entry數組
table = new Entry[INITIAL_CAPACITY];
//計算第一個Entry在數組中的索引
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//初始化第一個Entry
table[i] = new Entry(firstKey, firstValue);
size = 1;
//設置擴容的閾值(負載因子是2/3)
setThreshold(INITIAL_CAPACITY);
}
ThreadLocalMap維護了一個Entry數組。
下面看下ThreadLocalMap如何進行set操作
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//通過ThreadLocal的hashcode計算存放的數組的索引位置
int i = key.threadLocalHashCode & (len-1);
// 遍歷當前entry數組,看key對應的Entry是否已經存在,如果存在就覆蓋掉老的值
for (Entry e = tab[i]; //獲取一個Entry
e != null;
e = tab[i = nextIndex(i, len)]) {
//獲取當前entry保存的ThreadLocal弱引用對象
ThreadLocal<?> k = e.get();
//如果當前的key存在,則覆蓋原來的value
if (k == key) {
e.value = value;
return;
}
//如果key爲null,替換這個廢棄的Entry。(一般情況就是由於弱引用被垃圾回收機制回收了)
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//如果不存在key對應的Entry,則新生成一個Entry,並放到Entry[]中
tab[i] = new Entry(key, value);
int sz = ++size;
//如果Entry[]中,存在Entry的ThreadLocal引用爲null(一般是被被gc回收)的Entry,避免內存泄漏
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
二、Thread類對ThreadLocal的支持
Thread類維護了一個threadLocals,threadLocals爲當前線程維護一個ThreadLocal.ThreadLocalMap,使用這種方式,每個線程類都會有自己的一個threadLocals,這個threadLocals維護了當前線程的所有ThreadLocal。
ThreadLocal.ThreadLocalMap threadLocals = null;
三、ThreadLocal機制的內存機制
內存泄漏的原因
從上面的圖中可以看到,相關GC ROOT一共有三個,其中比較值得關注的是ThreadRef這個GC ROOT,因爲這個引用的生命週期依賴於線程的生命週期。
正常情況下,線程結束後,Thread對象,ThreadLocalMap對象,以及ThreadLocalMap對象中的Entry對象纔會被jvm回收(當線程退出時,Thread類執行清理操作,詳見Thread類的exit方法),但是如果這個線程不結束呢?那麼Entry對象將永遠不會被回收(典型的情況就是使用線程數固定的線程池)。
如何解決內存泄漏
Java爲了最小化減少內存泄露的可能性和影響,在ThreadLocal的get,set的時候都會清除線程Map裏所有key爲null的value。
所以最怕的情況就是,threadLocal對象設null了,開始發生“內存泄露”,然後使用線程池,這個線程結束,線程放回線程池中不銷燬,這個線程一直不被使用,或者分配使用了又不再調用get,set方法,那麼這個期間就會發生真正的內存泄露。
最直接的解決方法,在使用後及時調用ThreadLocal的remove()方法,這個方法會移除Entry數組中ThreadLocal對象對應的Entry對象,避免出現Entry(null->T對象)的情況。
附:SimpleDateFormat結合ThreadLocal實現線程安全(節選自其他文章,如使用最好先做下自測)
//SimpleDateFormat緩存
private static Map<String, ThreadLocal<SimpleDateFormat>> sdfMap =
new HashMap<String, ThreadLocal<SimpleDateFormat>>();
//對象鎖
private static ReentrantLock sdfLock = new ReentrantLock();
/**
* @Description: 推薦直接使用該方法 獲取 DateFormat對象
* 推薦理由:SimpleDateFormat非線程安全且生成開銷大
* @param pattern 格式規則
* @return DateFormat
*/
public static SimpleDateFormat getDateFormat(final String pattern) {
ThreadLocal<SimpleDateFormat> tl = sdfMap.get(pattern);
if (tl == null) {
try {
//最多10毫秒
if (!sdfLock.tryLock(10, TimeUnit.MILLISECONDS)) {
return new SimpleDateFormat(pattern);
}
tl = sdfMap.get(pattern);
if (tl == null) {
tl = new ThreadLocal<SimpleDateFormat>() {
//這裏重寫initialValue,第一次get就獲取該初始化,省去了set操作
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(pattern);
}
};
sdfMap.put(pattern, tl);
}
} catch (Exception exception) {
log.error(exception.getMessage());
} finally {
sdfLock.unlock();
}
}
return tl.get();
}
注意:每個線程都會有一個SimpleDateFormat對象,如果線程池中固定線程的數量很多,那麼SimpleDateFormat對象數量也會很多,可能造成內存泄漏,如果感覺不爽的話,用java8的LocalDate(LocalDateTime)
ThreadLocal設置爲static
ThreadLocal 實例通常是類中的 private static 字段,它們希望將狀態與某一個線程(例如,用戶 ID 或事務 ID)相關聯。
下面是ThreadLocal類中給出的一個示例。
public class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}
引用: