ThreadLocalは、同じ変数のアクセス競合の問題を解決するために生まれたため、これは、SpringのデフォルトのシングルトンBeanへのマルチスレッドアクセスに最適なソリューションです。Springはまた、ThreadLocalを使用して、複数のスレッドでの同じ変数の同時実行性のスレッドセーフの問題に対処します。
1.ThreadLocalの概要
次に、jdkの内容を見てください。このクラスはスレッドローカル変数を提供します。これらの変数は通常の変数とは異なります。各スレッドには独自の変数があり、ThreadLocalのgetまたはsetメソッドを介してアクセスされ、変数の独立して初期化されたコピーがあります。ThreadLocalインスタンスは通常、状態をスレッドに関連付けたいクラスのプライベート静的フィールドです。
2.ThreadLocalの実現原理
ThreadLocalクラスには、いくつかのメソッドが用意されています
。1.publicT get(){}
2.public void set(T value){}
3.public void remove(){}
4.protected T initialValue(){}
public T get()
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
//如果threadLocals变量不为null,就可以在map中查找到本地变量的值
if (map != null) {
//根据ThreadLocal的弱引用的key 查询ThreadLocalMap的Value值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果threadLocals为null,则初始化当前线程的threadLocals变量
return setInitialValue();
}
private T setInitialValue() {
//此处是返回一个null
T value = initialValue();
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
//如果threadLocals不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
if (map != null)
map.set(this, value);
//如果threadLocals为null,说明首次添加,需要首先创建出对应的map
else
createMap(t, value);
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
}
public void set(T value)
//话不多说,这个容易理解
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocal.ThreadLocalMap map = getMap(t);
if (map != null)
//这个是把ThreadLocal当做key,添加的属性值为value
map.set(this, value);
else
createMap(t, value);
}
public void remove()
ThreadLocalを使用してこのメソッドをアクティブに呼び出した後、メモリリークを防止します
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//当前线程根据ThrealLocal为key值删除
m.remove(this);
}
保護されたTinitialValue()
ThreadLocalを初期化するときにデフォルト値を設定するには、このメソッドをオーバーライドする必要があります
protected T initialValue() {
return null;
}
ThreadLocalの使用
1.jdkによって与えられた例を見てみましょう。
public class ThreadId {
private static final AtomicInteger nextId = new AtomicInteger(0);
private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
public static int get() {
return threadId.get();
}
}
class TestID{
public static void main(String[] args) {
int i = ThreadId.get();
System.out.println(i);//0
}
}
このケースを実行すると、操作の結果を確認できます。initialValue()が上記でオーバーライドされているため、0です。このメソッドがオーバーライドされていない場合、返される結果はnullになります。
2.スレッドの分離を確認します。
public class ThreadLocalDemo2 {
public static ThreadLocal t1 = new ThreadLocal();
}
class ThreadA extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
ThreadLocalDemo2.t1.set("TreadA " + (i + 1));
System.out.println("Thread get Value = " + ThreadLocalDemo2.t1.get());
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadB extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
ThreadLocalDemo2.t1.set("TreadB " + (i + 1));
System.out.println("Thread get Value = " + ThreadLocalDemo2.t1.get());
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Test {
public static void main(String[] args) {
try {
ThreadA a = new ThreadA();
ThreadB b = new ThreadB();
a.start();
b.start();
for (int i = 0; i < 100; i++) {
ThreadLocalDemo2.t1.set("Main " + (i + 1));
System.out.println("Main get Value " + ThreadLocalDemo2.t1.get());
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
このケースを実行して、結果を確認します。ThreadAスレッド、ThreadBスレッド、およびメインスレッドは、それぞれ独自のスレッドによって格納された値を取得します。ただし、呼び出しは同じThreadLocalオブジェクトであるため、ThreadLocalはスレッド間の分離を実現できると結論付けられます。
3. ThreadLocalの非継承性:
上記の例から、メインスレッドと子スレッドの間のデータは分離されており、継承できないことがわかります。次に、ThreadLocal InheritableThreadLocalのサブクラスが、この問題の解決策を提供します。
ケースを通して調べてみましょう:
public class ThreadLocalDemo6 {
public static InheritableThreadLocal<Long> inheritableThreadLocal = new InheritableThreadLocal() {
@Override
protected Long initialValue() {
return new Date().getTime();
}
};
}
class ThreadA6 extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
System.out.println("在ThreadA 线程中取值 : " + ThreadLocalDemo6.inheritableThreadLocal.get());
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Test6 {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
System.out.println("在线程main 中取值为: " + ThreadLocalDemo6.inheritableThreadLocal.get());
}
Thread.sleep(5000);
ThreadA6 threadA6 = new ThreadA6();
threadA6.start();
}
}
このケースを実行して、実行結果を確認します。メインスレッドによって設定された属性値を子スレッドで取得でき、InheritableThreadLocalが特性を継承していると結論付けることができます。
ThreadLocalMapから、ThreadLocalの不適切な使用によるメモリリークの問題を確認します。
ThreadLocalMapの内部実装を分析します
ThreadLocalMapの内部はEntry配列です。エントリのソースコードを見てみましょう。
/**
* Entry是继承自WeakReference的一个类,该类中实际存放的key是
* 指向ThreadLocal的弱引用和与之对应的value值(该value值
* 就是通过ThreadLocal的set方法传递过来的值)
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** value就是和ThreadLocal绑定的 */
Object value;
//k:ThreadLocal的引用,被传递给WeakReference的构造方法
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
上記から、ThreadLocalは弱参照であり、この参照はGCに抵抗できず、キーはリサイクルでき、値はリサイクルできず、メモリリークが発生する可能性があることがわかります(現時点ではThreadLocalMapにキーはnullですが、値はnullではありませんエントリアイテム)。
ThreadLocalの標準的な使用
ThreadLocal(set()またはget())を使用した後、それが使用されていない場合は、remove()メソッドを使用して、メモリリークを回避するために時間内にリソースをクリーンアップします。