ThreadLocalソース分析
23161 ワード
以下の解析はJDK 1.8に基づく.まず簡単なThreadLocalの使用例を見てみましょう.
上記コード出力:
getのコードはとても簡単です.mapを維持していることがわかります.このmapのkeyはthreadlocalオブジェクトです.このmapは、現在のスレッドオブジェクトから取得する必要があります.
このmapは、現在のスレッドオブジェクトに格納されます.
ThreadLocalMapは、ThreadLocalに定義された静的内部クラスです.このMapはHashmapではなく,このクラスの著者自身が実現したものである.まずEntryクラスの実装であり,EntryクラスはWeakReferenceクラスを継承する.
これは、このmapがスレッドオブジェクトに保存されているからです.処理しないと、スレッドオブジェクトが消えない限り、これらのkeyとvalueは永遠に存在します!WeakReferenceを使用すると、Entry内のkeyを外部参照することなく、このkey(ThreadLocalオブジェクト)は仮想マシンによって回収されます.しかしvalueはそうではありません.valueをクリアするにはresize操作まで待たなければなりません.entry配列をスキャンし、keyがnullであることを発見し、valueもnullに直接作成し、GCを保証する.
ThreadLocal実装ではロックは使用されていません.しかしAtomicIntegerを使用しています.これはThreadLocalクラスにサービスを提供するための静的メソッドであり、nextHashCodeである.クラスの初期nextHashCodeは0に設定され、呼び出しのたびに値が返され、0 x 61 c 88647が加算される.この値は、ThreadLocalオブジェクトの一意のシーケンス番号を示すために使用されます.
なぜThreadLocalには唯一の番号があるのですか?ThreadLocalは上のmapのkeyになるからです.mapの最下位は配列で実現されていることを知っています.ThreadLocalのシーケンス番号と上容量を使用すると、配列内のentryオブジェクトを迅速に位置決めできます.
メモリリークの問題
ThreadLocalはThreadLocalMapで弱参照としてEntryのKeyによって参照されているため、ThreadLocalが外部から強引用されて参照されていない場合、ThreadLocalは次回のJVMゴミ収集時に回収されます.このときEntryではKeyが回収されてnull Keyが1つ発生する場合があり、外部読み出しThreadLocalMapの要素はnull KeyではValueを見つけることができません.したがって、現在のスレッドのライフサイクルが長く、常に存在する場合、その内部のThreadLocalMapオブジェクトも常に生存しており、これらのnull keyには強い参照チェーンの関係がずっと存在しています.Thread-->ThreadLocalMap->Entry->Valueです.この強い参照チェーンはEntryが回収されず、Valueも回収されません.しかし、EntryのKeyはすでに回収されており、メモリが漏れています.しかし、JVMチームは、このような状況を考慮し、ThreadLocalのメモリ漏洩を最小限に抑えるための措置を講じています.ThreadLocalのget()、set()、remove()メソッド呼び出し時にスレッドThreadLocalMapのすべてのEntryのKeyがnullのValueを消去し、Entry全体をnullに設定し、次回のメモリ回収に役立ちます.
public class Test {
ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
ThreadLocal<String> stringLocal = new ThreadLocal<String>();
public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName());
}
public long getLong() {
return longLocal.get();
}
public String getString() {
return stringLocal.get();
}
public static void main(String[] args) throws InterruptedException {
final Test test = new Test();
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
Thread thread1 = new Thread() {
public void run() {
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
};
};
thread1.start();
thread1.join();
System.out.println(test.getLong());
System.out.println(test.getString());
}
}
上記コード出力:
1
main
11
Thread-0
1
main
getのコードはとても簡単です.mapを維持していることがわかります.このmapのkeyはthreadlocalオブジェクトです.このmapは、現在のスレッドオブジェクトから取得する必要があります.
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();
}
このmapは、現在のスレッドオブジェクトに格納されます.
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//thread 。
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMapは、ThreadLocalに定義された静的内部クラスです.このMapはHashmapではなく,このクラスの著者自身が実現したものである.まずEntryクラスの実装であり,EntryクラスはWeakReferenceクラスを継承する.
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
// WeakReference, k super, entry->key 。entry->value
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
これは、このmapがスレッドオブジェクトに保存されているからです.処理しないと、スレッドオブジェクトが消えない限り、これらのkeyとvalueは永遠に存在します!WeakReferenceを使用すると、Entry内のkeyを外部参照することなく、このkey(ThreadLocalオブジェクト)は仮想マシンによって回収されます.しかしvalueはそうではありません.valueをクリアするにはresize操作まで待たなければなりません.entry配列をスキャンし、keyがnullであることを発見し、valueもnullに直接作成し、GCを保証する.
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;
}
ThreadLocal実装ではロックは使用されていません.しかしAtomicIntegerを使用しています.これはThreadLocalクラスにサービスを提供するための静的メソッドであり、nextHashCodeである.クラスの初期nextHashCodeは0に設定され、呼び出しのたびに値が返され、0 x 61 c 88647が加算される.この値は、ThreadLocalオブジェクトの一意のシーケンス番号を示すために使用されます.
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);
}
なぜThreadLocalには唯一の番号があるのですか?ThreadLocalは上のmapのkeyになるからです.mapの最下位は配列で実現されていることを知っています.ThreadLocalのシーケンス番号と上容量を使用すると、配列内のentryオブジェクトを迅速に位置決めできます.
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);
}
メモリリークの問題
ThreadLocalはThreadLocalMapで弱参照としてEntryのKeyによって参照されているため、ThreadLocalが外部から強引用されて参照されていない場合、ThreadLocalは次回のJVMゴミ収集時に回収されます.このときEntryではKeyが回収されてnull Keyが1つ発生する場合があり、外部読み出しThreadLocalMapの要素はnull KeyではValueを見つけることができません.したがって、現在のスレッドのライフサイクルが長く、常に存在する場合、その内部のThreadLocalMapオブジェクトも常に生存しており、これらのnull keyには強い参照チェーンの関係がずっと存在しています.Thread-->ThreadLocalMap->Entry->Valueです.この強い参照チェーンはEntryが回収されず、Valueも回収されません.しかし、EntryのKeyはすでに回収されており、メモリが漏れています.しかし、JVMチームは、このような状況を考慮し、ThreadLocalのメモリ漏洩を最小限に抑えるための措置を講じています.ThreadLocalのget()、set()、remove()メソッド呼び出し時にスレッドThreadLocalMapのすべてのEntryのKeyがnullのValueを消去し、Entry全体をnullに設定し、次回のメモリ回収に役立ちます.