1.JAVA多線程(十)Java多線程之ThreadLocal
1.1 ThreadLocal類
ThreadLocal類主要解決的就是讓每個線程綁定自己的值,可以將ThreadLocal類形象的比喻成存放數據的盒子,盒子中可以存儲每個線程的私有數據。
如果你創建了一個ThreadLocal變量,那麼訪問這個變量的每個線程都會有這個變量的本地副本,這也是ThreadLocal變量名的由來。他們可以使用 get()和 set())方法來獲取默認值或將其值更改爲當前線程所存的副本的值,從而避免了線程安全問題。
每個線程往ThreadLocal中讀寫數據是線程隔離,互相之間不會影響的,由於不需要共享信息,自然就不存在競爭問題了,從而保證了某些情況下線程的安全,以及避免了某些情況需要考慮線程安全必須同步帶來的性能損失!
1.2 ThreadLocal示例
package com.yuanxw.chapter10;
import java.util.Random;
/**
* ThreadLocal
* 線程局部變量
*/
public class ThreadLocalExample {
private static ThreadLocal<String> defaultThreadLocal = new ThreadLocal(){
@Override
protected Object initialValue() {
return "==initialValue==";
}
};
private static ThreadLocal<String> threadLocal = new ThreadLocal();
public static void main(String[] args) throws InterruptedException {
System.out.println("獲得defaultThreadLocal默認值:"+defaultThreadLocal.get());
// 線程-A
Thread thread1 = new Thread(() -> {
// 設置【threadLocal】對象值爲:張三
threadLocal.set("張三");
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("【%s】線程-執行threadLocal值:【%s】", Thread.currentThread().getName(),threadLocal.get()));
}, "Thread-A");
// 線程-B
Thread thread2 = new Thread(() -> {
// 設置【threadLocal】對象值爲:李四
threadLocal.set("李四");
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("【%s】線程-執行threadLocal值:【%s】", Thread.currentThread().getName(),threadLocal.get()));
}, "Thread-B");
thread1.join();
thread2.join();
thread1.start();
thread2.start();
Thread.sleep(new Random().nextInt(1000));
System.out.println(String.format("【%s】線程-執行threadLocal值:【%s】", Thread.currentThread().getName(),threadLocal.get()));
}
}
執行結果:
獲得defaultThreadLocal默認值:==initialValue==
【Thread-B】線程-執行threadLocal值:【李四】
【Thread-A】線程-執行threadLocal值:【張三】
【main】線程-執行threadLocal值:【null】
1.3 ThreadLocal對應的底層結構圖
每個 Thread 都有一個 ThreadLocal.ThreadLocalMap 對象。當調用一個 ThreadLocal 的 set(T value) 方法時,先得到當前線程的 ThreadLocalMap 對象,然後將 ThreadLocal->value 鍵值對插入到該 Map 中,get() 方法類似。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
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();
}
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
1.3 ThreadLocal 內存泄露問題
在一些場景 (尤其是使用線程池) 下,ThreadLocal 沒有被外部強引用的情況下,在垃圾回收的時候,key 會被清理掉,而 value 不會被清理掉。這樣一來,ThreadLocalMap 中就會出現key爲null的Entry。假如我們不做任何措施的話,value 永遠無法被GC 回收,這個時候就可能會產生內存泄露。ThreadLocalMap實現中已經考慮了這種情況,在調用 set()、get()、remove() 方法的時候,會清理掉 key 爲 null 的記錄。使用完 ThreadLocal方法後 最好手動調用remove()方法。
– 以上爲《JAVA多線程(十)Java多線程之ThreadLocal》,如有不當之處請指出,我後續逐步完善更正,大家共同提高。謝謝大家對我的關注。
——厚積薄發(yuanxw)