一、ThreadLocal的簡介:
一般情況下,我們創(chuàng)建的變量都是可以給任何線程訪問并修改的,如果我們想讓線程擁有自己的私有本地變量,那我們就可以使用ThreadLocal類是實現(xiàn)這樣的想法。
ThreadLocal類主要解決的是讓每個線程綁定自己的值,可以將ThreadLocal類形象的比喻成存放數(shù)據(jù)的盒子,盒子中可以存儲每個線程的私有數(shù)據(jù)。
我們來看看一個示例代碼了解它的使用:
import java.text.SimpleDateFormat;
import java.util.Random;
/**
* ThreadLocal示例類
*/
public class ThreadLocalExample {
//SimpleDateFormat不是線程安全的,所以要為每個線程都創(chuàng)建一個本地副本
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){
//創(chuàng)建線程
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();
}
}
}

我們從結(jié)果中,可以看出,線程0使用了
set方法來修改后,變量的值變了,但是對于其他線程在沒有調(diào)用set方法之前,get取到的值都是一開始主線程初始化的默認(rèn)值。
二、ThreadLocal的實現(xiàn)原理
我們先來看看ThreadLocal類里面的一個很重要的靜態(tài)內(nèi)部類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我們可以將它當(dāng)作一個類似Map的數(shù)據(jù)結(jié)構(gòu),他的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;
...
默認(rèn)情況下這兩個變量都是null,只有當(dāng)前線程調(diào)用ThreadLocal類的set或get方法時才創(chuàng)建它們。
實際上的話,ThreadLocal存放的每一個線程的私有變量就是存在當(dāng)前線程對象的threadLocals對象中的,當(dāng)調(diào)用ThreadLocal的set方法時,就是先通過Thread.currentThread()來獲取當(dāng)前的線程t,然后再使用getMap(t)方法來獲取當(dāng)前線程對應(yīng)的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()方法的實現(xiàn):
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
這個方法就是直接獲取了線程對象里面的threadLocals,這個在我們剛剛看Thread類的時候已經(jīng)說到了這個對象了。
因此我們可以得出一個結(jié)論就是:最終的變量是放在了當(dāng)前線程的ThreadLocalMap
中,并不是存在ThreadLocal上,ThreadLocal可以理解為只是ThreadLocalMap的封裝,傳遞了變量值。ThrealLocal類中可以通過Thread.currentThread()獲取到當(dāng)前線程對象后,直接通過getMap(Thread t)可以訪問到該線程的ThreadLocalMap對象。
比如我們在同?個線程中聲明了兩個ThreadLocal對象的話,會使?Thread內(nèi)部都是使?僅有那個ThreadLocalMap存放數(shù)據(jù)的,ThreadLocalMap的key就是ThreadLocal對象,value就是ThreadLocal對象調(diào)?set?法設(shè)置的值。

三、關(guān)于ThreadLocal的內(nèi)存泄露的問題
我們來看看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中就會出現(xiàn)key為null的Entry。假如我們不做任何措施的話,value永遠(yuǎn)無法被GC回收,這個時候就可能會產(chǎn)?內(nèi)存泄露。ThreadLocalMap實現(xiàn)中已經(jīng)考慮了這種情況,在調(diào)用set()、get()、remove()方法的時候,會清理掉key為null的記錄。使用完ThreadLocal方法后最好手動調(diào)用remove()方法。