記事のディレクトリ
ThreadLocal
ThreadLocalは、スレッドローカル変数を提供するJDKパッケージによって提供されます。つまり、ThreadLocal変数を作成すると、この変数にアクセスするすべてのスレッドは、この変数のローカルコピーを持ちます。複数のスレッドがこの変数を操作する場合、実際には独自のローカルメモリ内の変数を操作するため、スレッドセーフの問題を回避できます。ThreadLocal変数を作成した後、各スレッドは変数を独自のローカルメモリにコピーします。
ThreadLocalの役割(用途は何ですか)?
並行性の問題は、複数のスレッドが同じ共有変数にアクセスする場合、特に複数のスレッドが共有変数に書き込む必要がある場合に特に発生しやすくなります。スレッドセーフを確保するために、一般ユーザーは共有変数にアクセスする際に適切な同期を行う必要があります。同期手段は通常、ロックを追加することです。これには、ユーザーがロックをある程度理解している必要があり、明らかにユーザーの負担が大きくなります。それで、変数が作成されたとき、各スレッドがそれにアクセスしたとき、それはそれ自身のスレッドの変数であるということを行う方法はありますか?実際、ThreadLocalはこれを解決することを意図していませんが、これを行うことができます。問題が発生します。
ThreadLocalの原理
まず、ThreadLocal関連クラスのクラス図構造を見てください。図
から、ThreadLocalMapタイプの変数であるthreadLocalsとinheritableThreadLocalsがThreadクラスにあり、ThreadLocalMapはカスタマイズされたハッシュマップです。デフォルトでは、各スレッドのこれら2つの変数はnullであり、現在のスレッドがThreadLocalsetまたはgetメソッドを初めて呼び出すときにのみ作成されます。実際、各スレッドのローカル変数はThreadLocalインスタンスではなく、呼び出し元のスレッドのthreadLocals変数に格納されます。つまり、ThreadLocal型のローカル変数は、特定のスレッドメモリ空間に格納されます。ThreadLocalはツールシェルであり、setメソッドを介して呼び出し元のスレッドのthreadLocalsに値の値を入れて保存します。呼び出し元のスレッドがgetメソッドを呼び出すと、現在のスレッドのthreadLocals変数から使用されます。呼び出し元のスレッドが終了しない場合、このローカル変数は常に呼び出し元のスレッドのthreadLocals変数に格納されるため、ローカル変数を使用する必要がない場合は、ThreadLocal変数のremoveメソッドを呼び出してローカル変数を削除できます。現在のスレッドのthreadLocalsからの変数。另外,Thread里面的threadLocals为何被设计为map结构?很明显是因为每个线程可以关联多个ThreadLocal变量
。
以下では、ThreadLocalのset、get、およびremoveメソッドの実装ロジックを簡単に分析します。
セットメソッド
コード(1)最初に呼び出し元のスレッドを取得し、次に現在のスレッドをパラメーターとして使用してgetMap(t)メソッドを呼び出します。getMap(t)の関数はスレッド自体の変数threadLocalsを取得することであり、 threadlocal変数は、のスレッドメンバー変数にバインドされています。
getMap(t)の戻り値が空でない場合は、値をthreadLocalsに設定します。つまり、現在の変数値を現在のスレッドのメモリ変数threadLocalsに入れます。ThreadLocalsはHashMap構造体であり、keyは現在のThreadLocalインスタンスのオブジェクト参照であり、valueはsetメソッドを介して渡される値です。getMap(t)がnull値を返す場合は、setメソッドが初めて呼び出され、この時点で現在のスレッドのthreadLocals変数が作成されることを意味します。
// set()方法
public void set(T value) {
// (1)获取当前线程
Thread t = Thread.currentThread();
// (2)将当前线程作为key,去查找对应的线程变量,找到则设置
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// (3)第一次调用就创建当前线程对应的HashMap
createMap(t, value);
}
// getMap()方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// createMap()方法
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
get()メソッド
public T get() {
// (4)获取当前线程
Thread t = Thread.currentThread();
//(5)获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
//(6)如果threadLocals不为null,则返回对应本地变量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// ( 7) threadLocals为空则初始化当前线程的threadLocals成员变量
return setInitialValue();
}
// setInitialValue()
private T setInitialValue() {
// (8)初始化为null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// (9)如果当前线程的threadLocals变量不为空
if (map != null)
map.set(this, value);
else
//(10)如果当前线程的threadLocals变量为空
createMap(t, value);
return value;
}
// initialValue()
protected T initialValue() {
return null;
}
remove()メソッド
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
InheritableThreadLocal
ThreadLocalは継承をサポートしていません。つまり、同じThreadLocal変数が親スレッドに設定された後は、子スレッドで取得できません。この問題を解決するために、InheritableThreadLocalが誕生しました。InheritableThreadLocalはThreadLocalを継承します。これは、子スレッドが親スレッドに設定されたローカル変数にアクセスできるようにする機能を提供します。InheritableThreadLocalのコードを見てみましょう。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
//(1)
protected T childValue(T parentValue) {
return parentValue;
}
//(2)
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
//(3)
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
上記のコードからわかるように、InheritableThreadLocalはThreadLocalを継承し、3つのメソッドを書き換えます。コード(3)から、InheritableThreadLocalがcreateMapメソッドを上書きしていることがわかります。setメソッドが初めて呼び出されると、threadLocalsではなく現在のスレッドのinheritableThreadLocals変数のインスタンスが作成されます。コード(2)は、getメソッドを呼び出して現在のスレッド内のマップ変数を取得すると、threadLocalsではなくinheritableThreadLocalsを取得することを示しています。
ThreadLocalメモリリークの問題
実際、ThreadLocalMapで使用されるキーは、ThreadLocalの弱参照です。弱参照の特徴は、このオブジェクトの弱参照しかない場合、次のガベージコレクションで必然的にクリーンアップされることです。
したがって、ThreadLocalが外部から強く参照されていない場合は、ガベージコレクション中にクリーンアップされるため、ThreadLocalMapでこのThreadLocalを使用するキーもクリーンアップされます。ただし、値は強力な参照であり、クリーンアップされません。このようにして、nullキーを持つ値が表示されます。
この状況は、ThreadLocalMapの実装で考慮されています。set()、get()、およびremove()メソッドが呼び出されると、キーがnullのレコードがクリーンアップされます。メモリリークが発生する場合は、キーを持つレコードがnullになった後、remove()メソッドが手動で呼び出されず、get()、set()、およびremove()メソッドが後で呼び出されなくなった場合のみです。 。
ThreadLocalRandomクラス
Randomクラスは、乱数の生成に役立つことがわかっています。たとえば、int型の複数の整数を生成するには、nextInt()メソッドを使用します。単一のスレッドでは問題はありません。マルチスレッドの場合はどうなりますか?まず、nextInt()のソースコードを見てみましょう。
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
oldseed = seed.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}
新しい乱数の生成には2つのステップが必要であることがわかります。
- まず、古いシードに基づいて新しいシードを生成します。
- 次に、新しいシードに基づいて新しい乱数を計算します。
oldseed = seed.get();
原子性の問題により、同時実行性が高い場合、複数のスレッドによって取得された古いシードが同じであり、生成された新しいシードが同じである可能性があり、最終的な結果として、生成された乱数も同じになります。
マルチスレッドの高い同時実行性の場合のRandomの欠点を補うために、JUCパッケージの下にThreadLocalRandomクラスが追加されました。
まず、ThreadLocalRandomのクラス図構造を見てください。
この図から、ThreadLocalRandomクラスがRandomクラスを継承し、nextIntメソッドをオーバーライドしていることがわかります。Randomクラスから継承されたアトミックシード変数は、ThreadLocalRandomクラスでは使用されません。ThreadLocalRandomに格納されている特定のシードはありません。特定のシードは、特定の呼び出しスレッドのthreadLocalRandomSeed変数に格納されています。ThreadLocalRandomは、ツールクラスであるThreadLocalクラスに似ています。スレッドがThreadLocalRandomの現在のメソッドを呼び出すと、ThreadLocalRandomは、初期化シードである呼び出し元スレッドのthreadLocalRandomSeed変数の初期化を担当します。
ThreadLocalRandomのnextIntメソッドが呼び出されると、現在のスレッドのthreadLocalRandomSeed変数が実際に現在のシードとして取得され、新しいシードが計算されます。次に、新しいシードが現在のスレッドのthreadLocalRandomSeed変数に更新され、次に乱数に更新されます。新しいシードに従って、特定のアルゴリズムを使用して計算されます。ここで、threadLocalRandomSeed変数はThreadクラスの通常のlong変数で
あり、アトミック変数ではないことに注意してください。実際、この変数はスレッドレベルであるため、理由は非常に単純です。したがって、ThreadLocalの原理をまだ理解していない場合は、アトミック変数を使用する必要はありません。