我們知道線程也是一個(gè)「對象」,當(dāng)線程這種對象想為我們提供一個(gè)「可以存取我們自定義變量的功能時(shí)」,來看下它是怎么做的。
一、Thread 中的成員變量ThreadLocalMap
-
這種功能通過成員變量
java.lang.Thread#threadLocals來完成的,它是一個(gè)自定義Map類型:
public class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; }可以看到它的:
-
null: 代表懶加載,只在你需要存數(shù)據(jù)時(shí)才會(huì)實(shí)例化一個(gè)ThreadLocalMap對象。 -
default: 訪問修飾符,代表只想讓java.lang下被訪問,就是給java.lang.ThreadLocal用。
-
-
為什么不在
Thread里面給我們暴露方法?設(shè)計(jì)者覺得暴露一個(gè)類似
get()/put()方法給你,讓你自己用任意的Object類型K/V操作太Low。所以他把這個(gè)功能「封裝」為一個(gè)類,稱為線程本地變量
java.lang.ThreadLocal,每一個(gè)你需要的變量就是一個(gè)此類的實(shí)例。
二、ThreadLocal 實(shí)現(xiàn)
它們的關(guān)系是,Thread has a ThreadLocalMap has a Entry has a ThreadLocal。
-
既然用
Map存,那Key和Value分別是什么?-
Key: 線程變量對象ThreadLocal。 -
Value: 你自定義操作的那個(gè)數(shù)據(jù)Object。
-
-
當(dāng)然,這個(gè)內(nèi)部
Map設(shè)計(jì)意圖肯定要對使用者透明所以我們不能直接操作這個(gè)
Map,而是使用的ThreadLocal的三個(gè)public方法:get()/set()/remove()set 方法
public void set(T value) { // 獲取當(dāng)前線程 Thread t = Thread.currentThread(); // 簡單的返回Thread 對象的成員變量threadLocals ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }get 方法
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { // 這個(gè)Map 沒有g(shù)et方法只有g(shù)etEntry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) // 你之前設(shè)置的值 return (T)e.value; } return setInitialValue(); }remove 方法
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
三、ThreaLocal 使用
-
Example
JavaDoc 中已經(jīng)給出了,摘抄一下:
import java.util.concurrent.atomic.AtomicInteger; public class ThreadId { // Atomic integer containing the next thread ID to be assigned private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread's ID private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }; // Returns the current thread's unique ID, assigning it if necessary public static int get() { return threadId.get(); } } -
解釋:初始化和
set()方法首先,之所以要使用「匿名內(nèi)部類」及其對象,是因?yàn)橄胱屇阍谝恍写a內(nèi)就完成初始化。除此之外,
initialValue()和set()的實(shí)現(xiàn)代碼區(qū)別不大。然后,一旦哪個(gè)
Thread引用到這個(gè)ThreadLocal對象了,就會(huì)在自己的threadLocals中被設(shè)置一個(gè)值。 獲取不必解釋,就是從當(dāng)前線程的Map中取即可。
四、WeakReference 的應(yīng)用
1. 假設(shè)我們來設(shè)計(jì)那個(gè)Map和其中的Entry
-
Map 和Entry
static class ThreadLocalMap { static class Entry<K,V> { final K key; V value; } } -
我們的使用場景是這樣的
// 這里注意,沒有final 了 private static ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }當(dāng)我們想不再使用
threadId時(shí),我們會(huì)把threadId設(shè)置為null:public void method1(){ // ... 一些操作 threadId = null; } -
此時(shí),GC 會(huì)清除這個(gè)對象嗎?
分析之后我們發(fā)現(xiàn)其實(shí)大概率不會(huì),因?yàn)槟莻€(gè)
ThreadLocal對象并非「不可達(dá)對象」,這是因?yàn)槲覀兊?code>Thread.threadLocals.Entry.key仍然引用著它。只有當(dāng)Thread對象本身被銷毀后它才會(huì)被回收,不過在Web 項(xiàng)目或RPC 項(xiàng)目中我們一般都使用線程池,所以一般來說線程很難被銷毀。很明顯,我們的設(shè)計(jì)有問題。那我們看看設(shè)計(jì)者是如何做的。
2. 實(shí)際代碼
-
來看看實(shí)際代碼
static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } // ... }Entry是個(gè)弱引用。這意味著:一旦GC 確定ThreadLocal對象是「弱可達(dá)對象」時(shí),它將清除所有引用此對象的「弱引用」,也就是說此時(shí)Entry也將被清除。 -
最后再來復(fù)習(xí)一下對象的「可到達(dá)性」
從最強(qiáng)到最弱,不同的可到達(dá)性級別反映了對象的生命周期。在操作上,可將它們定義如下:
- 強(qiáng)可到達(dá) 對象:如果某一線程可以不必遍歷所有引用對象而直接到達(dá)一個(gè)對象,則該對象是強(qiáng)可到達(dá) 對象。新創(chuàng)建的對象對于創(chuàng)建它的線程而言是強(qiáng)可到達(dá)對象。
- 軟可到達(dá) 對象:如果一個(gè)對象不是強(qiáng)可到達(dá)對象,但通過遍歷某一軟引用可以到達(dá)它,則該對象是軟可到達(dá) 對象。
- 弱可到達(dá) 對象:如果一個(gè)對象既不是強(qiáng)可到達(dá)對象,也不是軟可到達(dá)對象,但通過遍歷弱引用可以到達(dá)它,則該對象是弱可到達(dá) 對象。當(dāng)清除對某一弱可到達(dá)對象的弱引用時(shí),便可以終止此對象了。
- 虛可到達(dá) 對象:如果一個(gè)對象既不是強(qiáng)可到達(dá)對象,也不是軟可到達(dá)對象或弱可到達(dá)對象,它已經(jīng)終止,并且某個(gè)虛引用在引用它,則該對象是虛可到達(dá) 對象。
最后,當(dāng)不能以上述任何方法到達(dá)某一對象時(shí),該對象是不可到達(dá) 對象,因此可以回收此對象。