ThreadLocal
1 ThreadLocalの概要
多くのシナリオでは、明示的なパラメーターで定義する必要があるパラメーターを渡す必要がありますが、メソッドスタックが深い場合は、呼び出されたメソッドにユーザー情報を渡すなど、特にエレガントではありません。
//controller传递用户信息到serviceA
controller.serviceA(user);
//继续向serviceB传递
serviceA.serviceB(user);
//最终传递到dao层
serviceB.dao(user);
复制代码
各メソッドは明示的にユーザーパラメータを定義する必要がありますが、これは非常に肥大化しています。他の方法はありますか?
誰かがパブリック属性または静的変数を定義することを考えるかもしれませんが、これはマルチスレッド共有変数のスレッドの安全でない問題を引き起こすため、このパブリック属性をロックする必要があります。
ロックされると、効率は少し遅くはありませんが、より効率的な方法はありますか?このとき、*** ThreadLocal ***が使用されます。
上記の同期変数は統一されたガバナンスを採用していますが、ThreadLocalが採用している戦略は、分割して征服することです。
公式用語:ThreadLocalクラスは、スレッド内にローカル変数を提供するために使用されます。このような変数は、マルチスレッド環境(getまたはsetメソッドを介してアクセス)でアクセスされたときに、他のスレッドの変数から比較的独立していることが保証されています。ThreadLocalインスタンスは一般にprivate static
タイプであり、スレッドとスレッドコンテキストを関連付けるために使用されます。
簡単に言えば:
-
ThreadLocalは、スレッド内の変数のコピーを提供します。この変数は単一のスレッド内でのみ共有され、スレッド内のThreadLocal変数のコピーに簡単にアクセスできます。
-
複数のスレッド間のTreadLocal変数のコピーは、相互に影響しません。
-
ThreadLocalは、スレッドのライフサイクルでのみ存続し、スレッドの終了時に終了します(removeメソッドを手動で呼び出してThreadLocal変数を削除することもできます)。
このように、単一のスレッドで異なるメソッドにパラメーターを渡すことの複雑さは、各スレッド内の共有変数であり、マルチスレッドの不安定性の問題がないため、エレガントに解決されます。
次のことに注意してください。
-
ThreadLocalは同期メカニズムではなく、マルチスレッド化でのスレッドセーフティの問題を解決しません。
-
ThreadLocalは、各スレッドに変数の独立したコピーを提供することにより、複数のスレッドによるデータの同時アクセス競合を分離します。各スレッドには独自の変数のコピーがあるため、安全でないマルチスレッドの問題はありません。
上記のユーザー情報を渡す問題は、ThreadLocalを使用して解決できます。
public final class UserUtil {
private static ThreadLocal<User> userThreadLocal = new ThreadLocal();
public static User getUser() {
return userThreadLocal.get();
}
public static User setUser(User user) {
return userThreadLocal.set(user);
}
public static void removeUser() {
userThreadLocal.remove();
}
}
复制代码
//设置User
controller(){
UserUtil.setUser(user);
}
//获取User
serviceA(){
UserUtil.getUser();
}
serviceB(){
UserUtil.getUser();
}
dao(){
UserUtil.getUser();
}
复制代码
2 ThreadLocalの実現原理
ThreadLocalは、スレッドごとに独立したコピー変数を作成します。
ThreadLocalソースコードから、次の情報を取得できます。
*** get ***メソッドを呼び出して、ThreadLocalに対応する変数を取得します*** get ***メソッドのソースコードは次のとおりです。
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//根据线程获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//获取Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
//设置并返回初始值
return setInitialValue();
}
复制代码
このメソッドは、まず現在のスレッドを取得してから、現在のスレッドを通じて*** ThreadLocalMap ***を取得します。ThreadLocalMapが空の場合は、初期値を設定して戻ります。取得したマップが空でない場合、対応する値は現在の*** ThreadLocal ***に従って取得され、返されます。
この方法に従って、次の情報を取得できます。
-
ThreadLocalの対応する値は、*** ThreadLocalMap ***に格納されます。
-
ThreadLocalMapは、***現在のスレッド***インスタンスインスタンスに基づいて取得されます。
-
*** ThreadLocalMap ***がNULLであるか、ThreadLocalに対応する値がない場合は、初期値を返します(setInitialValueメソッド)。
ThreadLocalの値が*** ThreadLocalMap ***に存在することがわかったので、引き続きThreadLocalMap map = getMap(t);
このコードの特定の実装を調べます。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
复制代码
このメソッドは、スレッドの*** threadLocals ***属性を直接返します。さらに追跡すると、この属性が*** Thread ***クラスで定義されていることがわかります。
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
复制代码
*** ThreadLocalMapがThread ***に保存され、*** ThreadLocal ***に対応するキーと値のペアが*** ThreadLocalMap ***に保存されることは、ソースコードを通じてすでにはっきりとわかっています。概略図は次のとおりです。
3 ThreadLocalの部分的なソースコード分析
前のセクションでは、*** get ***メソッドのソースコードを分析してから、他の主要なソースコードを分析します。
setメソッド:
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
复制代码
set ***メソッドの機能は、現在の*** ThreadLocalインスタンス***をキーとして使用し、対応する値をキーと値のペアとして*** ThreadLocalMap ***に保存することです。*** ThreadLocalMap ***が作成されていない場合は、新しい*** ThreadLocalMapを作成します。
*** ThreadLocalMap ***を作成createMap(t, value)
します。具体的な実装方法は次のとおりです。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
复制代码
*** createMap ***メソッド*** new ***には*** ThreadLocalMap ***オブジェクトがあり、渡されるパラメーターは現在の*** ThreadLocal ***インスタンスと保存する必要のある変数値です。 *** ThreadLocalMap ***構築メソッドにトレースします。
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//创建Entry数组
table = new Entry[INITIAL_CAPACITY];
//计算元素位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//创建Entry
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
复制代码
*** ThreadLocalMap ***構築メソッドは、初期の長さ*** INITIAL_CAPACITY ***を使用して***エントリ***配列を作成し、初期要素のインデックスを計算します。
*** ThreadLocalキーと値のペア***を配列の対応する位置に保存した後、サイズを1に設定し、容量拡張の下限を初期化します。
このコードから、すべての*** ThrealLocalキーと値のペア***の最終的な保存場所は***エントリ配列***であることがわかります。エントリクラスは*** ThreadLocalMap ***クラスで定義されています。
public class ThreadLocal<T> {
...
static class ThreadLocalMap {
...
//弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
}
...
}
复制代码
Entryは*** WeakReference ***を継承しているため、ThreadLocalは弱い参照オブジェクトとして定義されているため、ThreadLocalへの強い参照がない限り、GCをトリガーするときにメモリが十分であるかどうかに関係なく、ThreadLocalがリサイクルされます。
しかし、現時点で問題があり、ThreadLocalがリサイクルされ、対応する***エントリ***の値がリサイクルされない場合、この値にはアクセスできません。リサイクルされるThreadLocalが増えるにつれて、回復できない値が増え、最終的にメモリリークが発生します。
一部の人々は言うでしょう、強力な参照を設計するのに十分ですか?実際、強い参照として設計しても効果はありません。参照を手動でnullに設定しないと、キー(ThreadLocal)と値が再利用されず、最終的にメモリリークが発生します。
強参照として設計されている場合も、値が手動でnullに設定され、キー(ThreadLocal)がリサイクルされていないため、メモリリークが発生します。
これがThreadLocalが弱参照オブジェクトとして設計されている理由です。値を手動で復元すると、ThreadLocalもリサイクルされます。これは間違いなく保険です。
上記の分析では、ThreadLocalを使用した後、手動で*** set()、get()、remove()***を呼び出す必要があることもよく言われます。そうしないと、メモリリークが発生します。
*** remove()***がこの問題を処理する方法を確認できます。
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) {
//清除key
e.clear();
//进一步清除
expungeStaleEntry(i);
return;
}
}
}
复制代码
*** remove()***はテーブルをループし、*** e.clear()***を呼び出してキーをクリアします。
そして、次の*** expungeStaleEntry ***メソッドはいくつかのことをします:
- ***値***と***テーブル***のi番目の要素を一緒にクリアします。
- テーブルの要素を循環して、キーがnullの要素を削除します
- テーブルの要素を再配置する
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
//设置value和tab[staleSlot]为null
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
Entry e;
int i;
//循环所有元素,并清除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 {
//重新排列元素位置
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;
}
复制代码
同様に、ThreadLocal *** set()、get()***メソッドは、最終的に*** expungeStaleEntry ***メソッドを呼び出して、キーの値がnullであることをクリアし、ここではそれらを繰り返さない。
4 ThreadLocalの拡張
ThreadLocalは、スレッド内のリソースの共有を解決し、スレッド間の***リソース分離を実現するために使用されますが、いくつかのシナリオでは、子スレッドのメインスレッドのリソースにアクセスする必要がありますが、これは実現できますか?もちろん、今度は別のクラスInheritabIeThreadLocalを使用する必要があります。
スレッドのソースコードでは、*** threadLocals ***属性の下に別の属性*** inheritableThreadLocals ***があることが簡単にわかります。
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
//继承map
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
...
}
复制代码
これら2つのプロパティのタイプは*** ThreadLocal.ThreadLocalMap ***であり、*** inheritableThreadLocals ***は*** InheritableThreadLocal ***クラスによって参照されます。
*** InheritableThreadLocal ***クラスのソースコードを見てみましょう
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
复制代码
InheritableThreadLocal ***は*** ThreadLocalを継承し、*** childValue、getMap、およびcreateMap ***の3つのメソッドをカバーします。
getMap、createMap ***が元の操作*** t.threadLocals ***から操作*** t.inheritableThreadLocalsに変更され、 *** childValue ***メソッドは*** ThreadLocalMap ***クラスに継承を作成しますマップで使用されます。
public class ThreadLocal<T> {
...
//创建一个继承map
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
...
//根据parentMap创建一个新的ThreadLocalMap,其中的元素key和value值相同
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
//循环创建新的Entry
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
//过滤key等于null的值
if (key != null) {
//childValue返回的值即是e.value
Object value = key.childValue(e.value);
//key,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++;
}
}
}
}
...
}
复制代码
createInheritedMap ***は、着信*** parentMap ***に基づいて新しい*** ThreadLocalMapをコピーし、 nullに等しいkeyの値を除外します。他の要素のkeyおよびvalue値は、親スレッドと一致しています。
スレッドはいつ継承マップを作成しましたか?スレッドが初期化されると、*** Thread ***クラスの*** init ***メソッドが呼び出され、*** inheritThreadLocals ***を*** true ***として指定すると、親スレッド** * inheritableThreadLocals *** nullと等しくない場合、スレッドは継承されたマップを作成します。
public class Thread implements Runnable {
....
//绝大部分情况下调用本初始化方法
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
//inheritThreadLocals 默认为 true
init(g, target, name, stackSize, null, true);
}
//线程初始化
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
//父类线程为创建本线程的运行线程
Thread parent = currentThread();
...
//创建继承map
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
}
...
}
复制代码
ここで注意が必要な点が3つあります。
-
ほとんどの場合、*** inheritThreadLocals ***のデフォルトはtrueです。
-
親
Thread parent = currentThread()
スレッドは、実行中のスレッドの新しいスレッドを作成します。つまり、現在実行中のスレッドは新しいスレッドを作成します。この時点では、実行中のスレッドはまだ実行中であり、新しいスレッドは初期化されていませんparent = currentThread()
。 -
子スレッドの*** inheritableThreadLocals ***は、親スレッドの*** inheritableThreadLocals ***に従ってコピーされます。これは、親スレッド*** inheritableThreadLocals ***値を渡すことと同じであり、子スレッドが実現されます親スレッドの値を取得します。
*** ThreadLocal ***と*** InheritableThreadLocal ***の実際の使用を例で比較してみましょう。
package com.gavin.test;
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
//设置主线程的值
ThreadLocalTest.threadLocal.set("threadLocal-main");
//启用线程1
new Thread(() -> {
//设置线程1的值
ThreadLocalTest.threadLocal.set("threadLocal-1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1==" + ThreadLocalTest.threadLocal.get());
}).start();
//启用线程2
new Thread(() -> {
//设置线程2的值
ThreadLocalTest.threadLocal.set("threadLocal-2");
System.out.println("线程2==" + ThreadLocalTest.threadLocal.get());
}).start();
System.out.println("主线程==" + ThreadLocalTest.threadLocal.get());
}
}
复制代码
このコードは*** threadLocal ***属性を定義し、この属性は3つのスレッドすべてに設定されます。出力は次のとおりです。
主线程==threadLocal-main
线程2==threadLocal-2
线程1==threadLocal-1
复制代码
3つのスレッドのthreadLocal属性は互いに干渉しないと結論付けることができるので、コードを変更して、子スレッドがメインスレッドの値を取得できるかどうかを確認してみましょう。
package com.gavin.test;
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
ThreadLocalTest.threadLocal.set("threadLocal-main");
new Thread(() -> {
//直接打印值
System.out.println("线程1==" + ThreadLocalTest.threadLocal.get());
}).start();
new Thread(() -> {
//直接打印值
System.out.println("线程2==" + ThreadLocalTest.threadLocal.get());
}).start();
System.out.println("主线程==" + ThreadLocalTest.threadLocal.get());
}
}
复制代码
結果は次のとおりです。
线程1==null
主线程==threadLocal-main
线程2==null
复制代码
その結果から、サブスレッドはメインスレッドのthreadLocal値を取得できないと結論付けることができ、スレッド間のthreadLocalが互いに分離されていることが再び証明されます。
子スレッドがメインスレッドの値にアクセスできるようにするために、*** inheritableThreadLocal ***を使用して達成しようとします。
package com.gavin.test;
public class InheritableThreadLocalTest {
private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
InheritableThreadLocalTest.inheritableThreadLocal.set("inheritableThreadLocal-main");
new Thread(() -> {
System.out.println("线程1==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
}).start();
new Thread(() -> {
System.out.println("线程2==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
}).start();
System.out.println("主线程==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
}
}
复制代码
結果は次のとおりです。
线程1==inheritableThreadLocal-main
主线程==inheritableThreadLocal-main
线程2==inheritableThreadLocal-main
复制代码
結果から、サブスレッドはメインスレッドの値を取得していると結論付けることができますが、サブスレッドの値が変更された場合、メインスレッドや他のサブスレッドに影響しますか?
上記のソースコード分析によると、メインスレッドは独自のinheritableThreadLocal ***を子スレッドに渡します。子スレッドは新しいEntryオブジェクトに再度入り、キーと値を保存するため、子スレッドの変更はメインスレッドの値に影響しません。他のサブスレッドには影響せず、独自のサブスレッドにのみ渡されるので、確認してみましょう。
package com.gavin.test;
public class InheritableThreadLocalTest {
private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
InheritableThreadLocalTest.inheritableThreadLocal.set("inheritableThreadLocal-main");
new Thread(() -> {
InheritableThreadLocalTest.inheritableThreadLocal.set("inheritableThreadLocal-1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
new Thread(() -> {
System.out.println("线程1的子线程==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
}).start();
}).start();
new Thread(() -> {
InheritableThreadLocalTest.inheritableThreadLocal.set("inheritableThreadLocal-2");
System.out.println("线程2==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
}).start();
System.out.println("主线程==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
}
}
复制代码
結果は以下の通りです
主线程==inheritableThreadLocal-main
线程2==inheritableThreadLocal-2
线程1==inheritableThreadLocal-1
线程1的子线程==inheritableThreadLocal-1
复制代码
私たちが推測したように:
- 子スレッドは*** InheritableThreadLocal ***を介してメインスレッドの値を取得できます(実際、これは新しいEntryオブジェクトです)。
- 子スレッドの変更*** InheritableThreadLocal ***値は、メインスレッドと他のスレッドの値に影響しません。
- 子スレッドの*** InheritableThreadLocal ***はその子スレッドに渡されます。
ナゲット列:juejin.im/user/5ba21d ...
著者:GavinKing
オリジナリティは簡単ではありません。転載については作者の同意を得て、著作権情報をご持参ください