一、ThreadLocal的簡介:
一般情況下,我們創建的變量都是可以給任何線程訪問並修改的,如果我們想讓線程擁有自己的私有本地變量,那我們就可以使用ThreadLocal
類是實現這樣的想法。
ThreadLocal
類主要解決的是讓每個線程綁定自己的值,可以將ThreadLocal類形象的比喻成存放數據的盒子,盒子中可以存儲每個線程的私有數據。
我們來看看一個示例代碼瞭解它的使用:
import java.text.SimpleDateFormat;
import java.util.Random;
/**
* ThreadLocal示例類
*/
public class ThreadLocalExample {
//SimpleDateFormat不是線程安全的,所以要爲每個線程都創建一個本地副本
private static final ThreadLocal<SimpleDateFormat> formarter =
ThreadLocal.withInitial(()->new SimpleDateFormat("yyyyMMdd HHmm"));
public static void main(String [] args) throws InterruptedException {
ThreadLocalExample example = new ThreadLocalExample();
for (int i = 0;i < 10; ++i){
//創建線程
Thread t = new Thread(()->{
System.out.println("Thread Name="+Thread.currentThread().getName()+" deafult Formatter=" +
formarter.get().toPattern());
try {
Thread.sleep(new Random().nextInt(1000));
}catch (Exception e){
e.printStackTrace();
}
formarter.set(new SimpleDateFormat());
System.out.println("Thread Name="+Thread.currentThread().getName()+" formatter=" +
formarter.get().toPattern());
},""+i);
Thread.sleep(new Random().nextInt(1000));
t.start();
}
}
}
我們從結果中,可以看出,線程0使用了
set
方法來修改後,變量的值變了,但是對於其他線程在沒有調用set
方法之前,get
取到的值都是一開始主線程初始化的默認值。
二、ThreadLocal的實現原理
我們先來看看ThreadLocal類裏面的一個很重要的靜態內部類ThreadLocalMap
:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
.......
這個ThreadLocalMap
我們可以將它當作一個類似Map
的數據結構,他的key爲ThreadLocal
對象,值爲Object
對象。
其實在Thread
類中也有兩個ThreadLocalMap
的對象,我們看看:
public
class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
...
默認情況下這兩個變量都是null,只有當前線程調用ThreadLocal類的set或get方法時才創建它們。
實際上的話,ThreadLocal
存放的每一個線程的私有變量就是存在當前線程對象的threadLocals
對象中的,當調用ThreadLocal
的set
方法時,就是先通過Thread.currentThread()
來獲取當前的線程t,然後再使用getMap(t)
方法來獲取當前線程對應的ThreadLocalMap
,然後對這個map進行操作的:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
我們來看看上面那個通過線程來獲取ThreadLocalMap
的getMap()
方法的實現:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
這個方法就是直接獲取了線程對象裏面的threadLocals
,這個在我們剛剛看Thread
類的時候已經說到了這個對象了。
因此我們可以得出一個結論就是:最終的變量是放在了當前線程的ThreadLocalMap
中,並不是存在ThreadLocal
上,ThreadLocal
可以理解爲只是ThreadLocalMap
的封裝,傳遞了變量值。ThrealLocal
類中可以通過Thread.currentThread()
獲取到當前線程對象後,直接通過getMap(Thread t)
可以訪問到該線程的ThreadLocalMap
對象。
比如我們在同⼀個線程中聲明瞭兩個ThreadLocal對象的話,會使⽤Thread內部都是使⽤僅有那個ThreadLocalMap存放數據的,ThreadLocalMap的key就是ThreadLocal對象,value就是ThreadLocal對象調⽤set⽅法設置的值。
三、關於ThreadLocal的內存泄露的問題
我們來看看ThreadLocalMap
中的鍵值對對象Entry
:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap
中使用的key爲ThreadLocal
的弱引用,而value是強引用。所以,如果ThreadLocal
沒有被外部強引用的情況下,在垃圾回收的時候,key會被清理掉,而value不會被清理掉。這樣⼀來,ThreadLocalMap
中就會出現key爲null的Entry
。假如我們不做任何措施的話,value永遠無法被GC回收,這個時候就可能會產⽣內存泄露。ThreadLocalMap
實現中已經考慮了這種情況,在調用set()、get()、remove()
方法的時候,會清理掉key爲null的記錄。使用完ThreadLocal
方法後最好手動調用remove()
方法。