ThreadLocal為解決多線程程序的并發(fā)問(wèn)題提供了一種新的思路瞧哟。
Synchronized用于線程間的數(shù)據(jù)共享熄赡,而ThreadLocal則用于線程間的數(shù)據(jù)隔離详炬。
從Handler中進(jìn)入ThreadLocal的世界
-
Android中一個(gè)線程只能有一個(gè)Looper姊舵,如果一個(gè)線程已經(jīng)有Looper晰绎,我們?cè)僭诰€程中調(diào)用Looper.prepare()方法會(huì)拋出
RuntimeException("Only one Looper may be created per thread")
。那如何確保一個(gè)線程中最多只能有一個(gè)Looper呢括丁?我們從Looper中尋找答案荞下。public final class Looper { static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { //如果該線程已經(jīng)有Looper if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } //該線程中沒(méi)有Looper sThreadLocal.set(new Looper(quitAllowed)); } ... }
從上面的源碼我們能知道如果一個(gè)線程已經(jīng)有Looper了
sThreadLocal.get()
便不為空就拋出異常。否則就會(huì)新建一個(gè)Looper然后set進(jìn)入sThreadLocal史飞。
ThreadLocal
-
sThreadLocal是一個(gè)靜態(tài)的成員變量尖昏,所有線程共享它。那它是如何實(shí)現(xiàn)同一個(gè)靜態(tài)變量在不同的線程中調(diào)用
get()
方法卻能返回不同值的騷操作的呢?讓我們來(lái)看看ThreadLocal的get()
和set()
public class ThreadLocal<T> { public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); } public void set(T value) { Thread t = Thread.currentThread(); //獲取Thread中的成員變量ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } private T setInitialValue() { //initialValue()返回null T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } ... }
public class Thread implements Runnable { ... ThreadLocal.ThreadLocalMap threadLocals = null; ... }
在每個(gè)Thread中都有一個(gè)ThreadLocalMap成員變量,ThreadLocal中的
get()
和set()
方法都是通過(guò)獲取到當(dāng)前線程的引用后直接再通過(guò)getMap()
方法拿到ThreadMap的引用庐冯。ThreadLocal就是通過(guò)能獲取到每個(gè)線程中的ThreadLocalMap,從而實(shí)現(xiàn)線程間的數(shù)據(jù)隔離迹淌。ThreadLocalMap只能通過(guò)ThreadLocal的createMap()
方法初始化河绽。就讓我們來(lái)看看ThreadLocalMap。
ThreadLocalMap
-
ThreadLocalMap是ThreadLocal的內(nèi)部類唉窃。它用來(lái)存儲(chǔ)數(shù)據(jù)耙饰,采用類似hashmap機(jī)制,存儲(chǔ)了以ThreadLocal為key句携,需要隔離的數(shù)據(jù)為value的Entry鍵值對(duì)數(shù)組結(jié)構(gòu)榔幸。里面有一些具體關(guān)于如何清理過(guò)期的數(shù)據(jù)、擴(kuò)容等機(jī)制矮嫉,思路基本和hashmap差不多,有興趣的可以自行閱讀了解牍疏。
static class ThreadLocalMap { ... //存儲(chǔ)放入的數(shù)據(jù) private Entry[] table; //Entry繼承ThreadLocal的弱引用 static class Entry extends WeakReference<ThreadLocal> { //要存儲(chǔ)的變量 Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } private Entry getEntry(ThreadLocal key) { //獲取存儲(chǔ)數(shù)據(jù)的位置 int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } private void set(ThreadLocal key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); //遍歷整個(gè)Entry數(shù)組 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal k = e.get(); if (k == key) { //覆蓋數(shù)據(jù) e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } //如果位置為空蠢笋,就新建有要存儲(chǔ)的Entry后放入數(shù)組 tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } ... }
ThreadLocal實(shí)例被線程的ThreadLocalMap實(shí)例持有,也可以看成被線程持有鳞陨。但是ThreadLocalMap的key是ThreadLocal實(shí)例的弱引用昨寞。從而避免了內(nèi)存泄漏。
總結(jié)
- 每個(gè)線程中都有一個(gè)獨(dú)立的ThreadLocalMap副本厦滤,它所存儲(chǔ)的值援岩,只能被當(dāng)前線程讀取和修改。ThreadLocal類通過(guò)操作每一個(gè)線程特有的ThreadLocalMap副本掏导,從而實(shí)現(xiàn)了變量訪問(wèn)在不同線程中的隔離享怀。每個(gè)Thread只訪問(wèn)自己的 Map,那就不存在多線程寫的問(wèn)題趟咆,也就不需要鎖添瓷。
- 每個(gè)線程Thread中都有一個(gè)成員變量ThreadLocalMap,ThreadLocalMap底層是一個(gè)數(shù)組實(shí)現(xiàn)的值纱×鄞可以創(chuàng)建不同的ThreadLocal作為Key,存儲(chǔ)對(duì)應(yīng)Value
- 與同步機(jī)制比較:對(duì)于多線程資源共享的問(wèn)題虐唠,同步機(jī)制采用了“以時(shí)間換空間”的方式搀愧,而ThreadLocal采用了“以空間換時(shí)間”的方式。前者僅提供一份變量疆偿,讓不同的線程排隊(duì)訪問(wèn)咱筛,而后者為每一個(gè)線程都提供了一份變量,因此可以同時(shí)訪問(wèn)而互不影響翁脆。
- 在很多情況下眷蚓,ThreadLocal比直接使用synchronized同步機(jī)制解決線程安全問(wèn)題更簡(jiǎn)單,更方便反番,且結(jié)果程序擁有更高的并發(fā)性沙热。