- 實(shí)現(xiàn)原理
ThreadLocal可以看做是一個(gè)容器,容器里面存放著屬于當(dāng)前線程的變量鹏往。
ThreadLocal類提供了四個(gè)接口方法,這也是用戶操作ThreadLocal類的基本方法:
(1) void set(Object value)設(shè)置當(dāng)前線程的線程局部變量的值。
(2) public Object get()該方法返回當(dāng)前線程所對(duì)應(yīng)的線程局部變量涯鲁。
(3) public void remove()將當(dāng)前線程局部變量的值刪除鸭栖,目的是為了減少內(nèi)存的占用歌馍,該方法是JDK 5.0新增的方法。需要指出的是晕鹊,當(dāng)線程結(jié)束后松却,對(duì)應(yīng)該線程的局部變量將自動(dòng)被垃圾回收暴浦,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作,但它可以加快內(nèi)存回收的速度晓锻。防止內(nèi)存泄漏歌焦。例如在線程池中,可能該線程一直存在砚哆,線程中的ThreadLocal對(duì)象也會(huì)一直存在独撇。
(4) protected Object initialValue()返回該線程局部變量的初始值,該方法是一個(gè)protected的方法躁锁,顯然是為了讓子類覆蓋而設(shè)計(jì)的纷铣。這個(gè)方法是一個(gè)延遲調(diào)用方法,在線程第1次調(diào)用get()或set(Object)時(shí)才執(zhí)行战转,并且僅執(zhí)行1次搜立,ThreadLocal中的缺省實(shí)現(xiàn)直接返回一個(gè)null。
ThreadLocal內(nèi)部是如何為每一個(gè)線程維護(hù)變量副本的呢槐秧?其實(shí)在Thread類中有一個(gè)成員變量ThreadLocal.ThreadLocalMap threadLocals 存儲(chǔ)線程實(shí)例的變量副本啄踊。ThreadLocalMap是ThreadLocal類的靜態(tài)內(nèi)部類。這個(gè)變量threadLocals的維護(hù)都是由ThreadLocal操作刁标。
但是ThreadLocalMap不是map類型颠通,而是數(shù)組類型,通過(guò)ThreadLocal對(duì)象的hashcode計(jì)算對(duì)應(yīng)的數(shù)組索引膀懈,數(shù)組存儲(chǔ)的value是線程中變量的副本顿锰,每個(gè)線程可能存在多個(gè)ThreadLocal對(duì)象。
public T get() {
Thread t = Thread.currentThread();//當(dāng)前線程
ThreadLocalMap map = getMap(t);//獲取當(dāng)前線程對(duì)應(yīng)的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//獲取對(duì)應(yīng)ThreadLocal的變量值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();//若當(dāng)前線程還未創(chuàng)建ThreadLocalMap吏砂,則返回調(diào)用此方法并在其中調(diào)用createMap方法進(jìn)行創(chuàng)建并返回初始值撵儿。
}
//設(shè)置變量的值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
為當(dāng)前線程創(chuàng)建一個(gè)ThreadLocalMap的threadlocals,并將第一個(gè)值存入到當(dāng)前map中
@param t the current thread
@param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//刪除當(dāng)前線程中ThreadLocalMap對(duì)應(yīng)的ThreadLocal
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
上述是在ThreadLocal類中的幾個(gè)主要的方法,他們的核心都是對(duì)其內(nèi)部類ThreadLocalMap進(jìn)行操作狐血,下面看一下該類的源代碼:
static class ThreadLocalMap {
//map中的每個(gè)節(jié)點(diǎn)Entry,其鍵key是ThreadLocal并且還是弱引用淀歇,這也導(dǎo)致了后續(xù)會(huì)產(chǎn)生內(nèi)存泄漏問題的原因。
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
/**
* 初始化容量為16匈织,以為對(duì)其擴(kuò)充也必須是2的指數(shù)
*/
private static final int INITIAL_CAPACITY = 16;
/**
* 真正用于存儲(chǔ)線程的每個(gè)ThreadLocal的數(shù)組浪默,將ThreadLocal和其對(duì)應(yīng)的值包裝為一個(gè)Entry。
*/
private Entry[] table;
}
- 內(nèi)存泄漏問題
線程執(zhí)行完操作后如果只是將ThreadLocal對(duì)象置為null缀匕。例如 ThreadLocal<Integer> seqNum = null纳决。會(huì)造成內(nèi)存泄漏。原因如下:ThreadLocal是個(gè)容器乡小,容器里面的對(duì)象沒有處理阔加。容器里面的對(duì)象的引用存儲(chǔ)在ThreadLocalMap中。如果線程操作完后满钟,還一直存在在線程池里面胜榔,這個(gè)線程的ThreadLocalMap也一直存在胳喷,ThreadLocalMap里面的變量副本的引用也一直存在。這樣導(dǎo)致變量副本也一直存在夭织,造成內(nèi)存泄漏吭露。
解決方法:ThreadLocal對(duì)象使用完之后調(diào)用ThreadLocal.remove()。ThreadLocal對(duì)象的remove()方法會(huì)把ThreadLocalMap中對(duì)應(yīng)的變量副本的引用刪掉尊惰。