ThreadLocal天生爲解決相同變量的訪問衝突問題, 所以這個對於spring的默認單例bean的多線程訪問是一個完美的解決方案。spring也確實是用了ThreadLocal來處理多線程下相同變量併發的線程安全問題。
1. ThreadLocal的簡介
那麼看看jdk是怎麼說的:此類提供線程局部變量,這些變量與普通變量不同,每個線程都有自己的變量,通過ThreadLocal的get或者set方法訪問,有獨立初始化的變量副本。ThreadLocal實例通常是希望將狀態與線程關聯的類中的私有靜態字段。
2.ThreadLocal的實現原理
ThreadLocal類中提供了幾個方法:
1.public T 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);
}
protected T initialValue()
對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
}
}
執行此案例可以看到運行結果:是0 ,因爲上面重寫了initialValue() ,如果沒有重寫此方法,則返回結果爲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線程、和Main線程都各自獲取自己線程存儲的值。 但是調用的卻是同一個ThreadLocal對象,那麼得出結論,ThreadLocal可以實現線程間的隔離。
3、ThreadLocal的不可繼承性:
由上面的例子可以看出,main線程和子線程間的數據是隔離的,並不能實現繼承,那麼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();
}
}
執行此案例可以看到運行結果:main線程設置的屬性值,在子線程中可以獲取到,那麼可以得出InheritableThreadLocal 具有繼承的特性。
從ThreadLocalMap看ThreadLocal使用不當的內存泄漏問題
分析ThreadLocalMap內部實現
ThreadLocalMap的內部是一個Entry數組,下面我們來看一下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,key可以被回收,那麼value 確不能被回收,這就可能造成內存泄漏(因爲這個時候ThreadLocalMap會存在key爲null但是value不爲null的entry項)。
規範使用ThreadLocal
在使用ThreadLocal(set()或者get()) 後,如果不在使用則要及時使用remove()方法清理資源,避免內存泄漏的問題。