複数のスレッドが ThreadLocal の値を変更するとどうなりますか?
テストコードを書きます。
public class ThreadLocalTest {
public static void main(String[] args) {
ThreadLocal<String> sharedName = new ThreadLocal<>();
ThreadLocal<Integer> sharedId = new ThreadLocal<>();
//main线程最先修改
System.out.println(Thread.currentThread().getName() + "初始值:" + sharedName.get()+" "+sharedId.get());
sharedName.set("main");
sharedId.set(103);
System.out.println(Thread.currentThread().getName() + "设定后:" + sharedName.get()+" "+sharedId.get()+"\n");
Thread tA = new Thread() {
@Override
public void run() {
System.out.println(this.getName() + "开始值:" + sharedName.get()+" "+sharedId.get());
sharedName.set("线程A");
sharedId.set(101);
sleepTime(100);
System.out.println(this.getName() + "设定后:" + sharedName.get()+" "+sharedId.get()+"\n");
}
};
Thread tB = new Thread() {
@Override
public void run() {
sleepTime(100);
System.out.println(this.getName() + "开始值:" + sharedName.get()+" "+sharedId.get());
sharedName.set("线程B");
sharedId.set(102);
System.out.println(this.getName() + "设定后:" + sharedName.get()+" "+sharedId.get()+"\n");
}
};
tA.start();
tB.start();
}
private static void sleepTime(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
出力結果:
main初始值:null null
main设定后:main 103
Thread-0开始值:null null
Thread-0设定后:线程A 101
Thread-1开始值:null null
Thread-1设定后:线程B 102
プログラムには main、tA、tB の 3 つのスレッドがあり、3 つのスレッドすべてでsharedNameとsharedId (以下、nameとidと呼びます)が変更されていますが、メインスレッドtAとtBによって設定されたnameとidが読み込まれていません。 ta および tB の開始読み取りは null です。
tA スレッドがメインスレッドによって設定された名前を読み取れないのはなぜですか?
ThreadLoca.set() メソッドを見てください。
//ThreadLocal.java
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 getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//ThreadLocalMap是ThreadLocal的静态内部类
//ThreadLocalMap的构造器
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);
}
getMap()
set メソッドが現在のスレッドの ThreadLocalMap を取得し、設定された文字列「main」を <key, value> の形式で ThreadLocalMap に格納することがわかります。以下の Thread のソース コードを見ると、各 Thread に ThreadLocalMap 型のメンバー変数があることがわかります。
public
class Thread implements Runnable {
...
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
したがって、保存する場合は、現在のスレッドの ThreadLocalMap に保存されます。読むときはどう読みますか?
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();
}
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);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
protected T initialValue() {
return null;
}
get() メソッドが読み取られると、現在のスレッドの ThreadLocalMap も読み取られることがわかります。したがって、tA スレッドが読み込む名前は tA スレッド自体の ThreadLocalMap になります。最初はマップが空なので名前は null です。値を設定した後は、自分で設定した値を読み込みます。
明らかに同じ ThreadLocal オブジェクトですが、なぜ異なる名前の値が取得されるのでしょうか?
これは、マップ A とマップ B という 2 つの異なる HashMap のようなもので、両方とも同じキー値を持つ Entry のセットを持っていますが、値が異なるため、読み取られる値も異なります。下図を見ると一目瞭然ですが、tlはThreadLocalの略です。
結論:スレッドは、ThreadLocal によって制御される名前を読み取ります。ThreadLocal は、スレッド自身の ThreadLocalMap を取得するため、tA スレッドは、自身の ThreadLocalMap の名前の値も読み取ります。メインスレッドによって設定された名前の値を読み取ることができません。