|
1、看個熱鬧 ? ? ? ?鑒于普羅大眾都喜歡看熱鬧,咱們先來看個熱鬧再開工吧! ? 場景一:? 中午了, 張三、李四和王五一起去食堂大菜吃飯。食堂剛經營不久,還很簡陋,負責打菜的只有一位老阿姨。 ?張三:我要一份雞腿。 ?李四:我要一份小雞燉蘑菇。 ?張三:我再要一份紅燒肉。 ?王五:我要一份紅燒排骨。 ?李四:我不要小雞燉蘑菇了,換成紅燒鯽魚。 ?王五:我再要一份椒鹽蝦。 ?張三:我再要一份梅菜扣肉。 ?...... ?張三:我點的紅燒肉,為啥給我打紅燒鯽魚? ?李四:我的紅燒鯽魚呢? ?王五:我有點紅燒肉嗎? ?...... ?李四:我點了15元的菜,為啥扣我20? ?王五:我點了20元的菜,只扣了我15元,賺了,竊喜! ?張三:我已經刷了卡了,怎么還叫我刷卡? ?...... ?老阿姨畢竟上了年紀,不那么利索,這幾個小伙子咋咋呼呼,快言快語,老阿姨也被攪暈了,手忙腳亂,忙中出錯,這仨小伙也是怨聲載道。 ?場景二: ?食堂領導看到這個場景,趕緊要求大家排隊,一個一個來。后來,老阿姨輕松多了,也沒有再犯錯了。 ?但是,新的問題又來了,打菜的人當中,很多妹子很磨嘰,點個菜猶猶豫豫想半天。 ?張三:太慢了,我快餓死了! ?李四:再這么慢,下次去別家! ?王五:我等得花兒都謝啦! ?趙六:啥?我點了啥菜,花了多少錢,其它人怎么都知道?是阿姨多嘴了,還是其它人偷偷關注我很久了?太不安全了,一點隱私都沒有,以后不來了。 ?...... ?場景三: ? 領導聽到這些怨言,心里很不是滋味,大手一揮:擴大經營,以后為你們每一個人開一個流動窗口并請一位私人阿姨,只為你一個人服務! ? 從此,再也沒有怨言,阿姨也沒有再犯錯了,皆大歡喜...... ? ? ? ? ?場景一就像多個線程同時去操作一個數(shù)據(jù),最終的結果就是混亂。于是出現(xiàn)了同步鎖synchronized,同一時刻只運行一個線程操作,就像場景二,大家先來后到排隊,混亂的問題解決了。但是此時一個線程在操作的時候,其它線程只能閑等著,而且這些數(shù)據(jù)是共享的,每個線程希望擁有只能自己操作的私人數(shù)據(jù),ThreadLocal就正好滿足了這個需求。 ? ? ? ?所以,相比于synchronized,Threadlocal通過犧牲空間來換取時間和效率。 ? 2、ThreadLocal簡介? ? ? ? ?ThreadLocal官方的介紹為: 1 /**
2 * This class provides thread-local variables. These variables differ from
3 * their normal counterparts in that each thread that accesses one (via its
4 * {@code get} or {@code set} method) has its own, independently initialized
5 * copy of the variable. {@code ThreadLocal} instances are typically private
6 * static fields in classes that wish to associate state with a thread (e.g.,
7 * a user ID or Transaction ID).
8 */
? ? ? ?大致意思是:ThreadLocal提供了線程本地變量。這些變量與一般變量相比,其不同之處在于,通過它的get()和set()方法,每個線程可以訪問自己獨立擁有的初始變量副本。翻譯成人話就是,ThreadLocal為每一個線程開辟了一個獨立的存儲器,只有對應的線程才能夠訪問其數(shù)據(jù),其它線程則無法訪問。對應于前文的場景,就像食堂為每一個人安排了一個窗口和專屬阿姨為其打菜,這個過程中,這個窗口和阿姨就是其專屬的獨立的資源,其他人就無從知道他點了什么菜,花了多少錢。 ? 3、ThreadLocal的簡單使用示例? ? ? 是騾子是馬,先拉出來溜溜!先直觀看看它的能耐,再來了解它豐富的內心: 1 // =========實例3.1========
2 private ThreadLocal<String> mThreadLocal = new ThreadLocal<>();
3 private void testThreadLocal() throws InterruptedException {
4 mThreadLocal.set("main-thread");
5 Log.i("threadlocaldemo", "result-1=" mThreadLocal.get());
6 Thread thread_1 = new Thread() {
7 @Override
8 public void run() {
9 super.run();
10 mThreadLocal.set("thread_1");
11 Log.i("threadlocaldemo", "result-2=" mThreadLocal.get());
12 }
13 };
14 thread_1.start();
15 //該句表示thread_1執(zhí)行完后才會繼續(xù)執(zhí)行
16 thread_1.join();
17 Thread thread_2 = new Thread() {
18 @Override
19 public void run() {
20 super.run();
21 Log.i("threadlocaldemo", "result-3=" mThreadLocal.get());
22 }
23 };
24 thread_2.start();
25 //該句表示thread_2執(zhí)行完后才會繼續(xù)執(zhí)行
26 thread_2.join();
27 Log.i("threadlocaldemo", "result-4=" mThreadLocal.get());
28 }
?在主線程中調用這個方法,運行結果: 1 12-13 13:42:50.117 25626-25626/com.example.demos I/threadlocaldemo: result-1=main-thread 2 12-13 13:42:50.119 25626-25689/com.example.demos I/threadlocaldemo: result-2=thread_1 3 12-13 13:42:50.119 25626-25690/com.example.demos I/threadlocaldemo: result-3=null 4 12-13 13:42:50.120 25626-25626/com.example.demos I/threadlocaldemo: result-4=main-thread ? ? ? ?看到這個結果會不會驚掉下巴呢?明明在第9行中set了值,第10行中也得到了對應的值,但第20行的get得到的卻是null,第26行得到的是第3行set的值。這就是ThreadLocal的神奇功效,主線程set的值,只能在主線程get到;thread_1內部set的值,thread_1中才能get;thread_2中沒有set,所以get到的就是null。 ? ? ? ?而實現(xiàn)這,不要999,也不要99,只要3......三步即可: 1 ThreadLocal<T> mThreadLocal = new ThreadLocal<>(); 2 mThreadLocal.set(T); 3 mThreadLocal.get(); 就是這么方便,就是這么簡潔! ? 4、提供的4個主要接口 ? ? ? ?ThreadLocal以其使用簡單,風格簡潔讓人一見傾心。它對外提供的接口很少,當前SDK中,主要有4個: 1 public void set(T value) { }
2 public T get() { }
3 public void remove() { }
4 protected T initialValue() { }
為了保持對這些方法說明的原滋原味,我們直接通過源碼中對其的注釋說明來認識它們。 ?(1)set() 1 /**
2 * Sets the current thread's copy of this thread-local variable
3 * to the specified value. Most subclasses will have no need to
4 * override this method, relying solely on the {@link #initialValue}
5 * method to set the values of thread-locals.
6 *
7 * @param value the value to be stored in the current thread's copy of
8 * this thread-local.
9 */
10 public void set(T value)
設置當前線程的ThreadLocal值為指定的value。大部分子類沒有必要重寫該方法,可以依賴initialValue()方法來設置ThreadLocal的值。 ? (2)get() 1 /**
2 * Returns the value in the current thread's copy of this
3 * thread-local variable. If the variable has no value for the
4 * current thread, it is first initialized to the value returned
5 * by an invocation of the {@link #initialValue} method.
6 *
7 * @return the current thread's value of this thread-local
8 */
9 public T get()
用于獲取當前線程所對應的ThreadLocal值。如果當前線程下,該變量沒有值,會通過調用initialValue()方法返回的值對其進行初始化。 ? (3)remove() 1 /**
2 * Removes the current thread's value for this thread-local
3 * variable. If this thread-local variable is subsequently
4 * {@linkplain #get read} by the current thread, its value will be
5 * reinitialized by invoking its {@link #initialValue} method,
6 * unless its value is {@linkplain #set set} by the current thread
7 * in the interim. This may result in multiple invocations of the
8 * {@code initialValue} method in the current thread.
9 *
10 * @since 1.5
11 */
12 public void remove()
? ? ? ?該接口是從JDK1.5開始提供的,用于刪除當前線程對應的ThreadLocal值,從而減少內存占用。在同一線程中,如果該方法被調用了,隨后再調用get()方法時,會使得initialValue()被調用,從而ThreadLocal的值被重新初始化,除非此時在調用get()前調用了set()來賦值。該方法可能導致initialValue()被多次調用。該方法可以不用顯示調用,因為當線程結束后,系統(tǒng)會自動回收線程局部變量值。所以該方法不是必須調用的,只不過顯示調用可以加快內存回收。 ? (4)initialValue() 1 /**
2 * Returns the current thread's "initial value" for this
3 * thread-local variable. This method will be invoked the first
4 * time a thread accesses the variable with the {@link #get}
5 * method, unless the thread previously invoked the {@link #set}
6 * method, in which case the {@code initialValue} method will not
7 * be invoked for the thread. Normally, this method is invoked at
8 * most once per thread, but it may be invoked again in case of
9 * subsequent invocations of {@link #remove} followed by {@link #get}.
10 *
11 * <p>This implementation simply returns {@code null}; if the
12 * programmer desires thread-local variables to have an initial
13 * value other than {@code null}, {@code ThreadLocal} must be
14 * subclassed, and this method overridden. Typically, an
15 * anonymous inner class will be used.
16 *
17 * @return the initial value for this thread-local
18 */
19 protected T initialValue() {
20 return null;
21 }
? ? ? ?返回當前線程對應的ThreadLocal的初始值。當當前線程是通過get()方法第一次對ThreadLocal進行訪問時,該方法將會被調用,除非當前線程之前調用過set()方法,在這種情況下initialValue()方法將不會被當前線程所調用。一般而言,該方法最多只會被每個線程調用一次,除非隨后在當前線程中調用remove()方法,然后調用get()方法。該實現(xiàn)會簡單地返回null;如果程序員希望ThreadLocal擁有一個初始值,而不是null,ThreadLocal需要定義一個子類,并且在子類中重寫initialValue()方法。比較典型的做法是使用一個匿名內部類。該方法由protected修飾,可見其這樣設計通常是為了供用戶重寫,從而自定義初始值。后面會再通過實例來演示該方法的使用。 ? 5、ThreadLocal工作機制 ? ? ? ?ThreadLocal使用起來非常簡單,但它是如何實現(xiàn)為每一個Thread保存一份獨立的數(shù)據(jù)的呢?我們先結合實例3.1來看set()方法都做了些什么: 1 //=========ThreadLocal=======源碼5.1
2 public void set(T value) {
3 Thread t = Thread.currentThread();
4 ThreadLocalMap map = getMap(t);
5 if (map != null)
6 map.set(this, value);
7 else
8 createMap(t, value);
9 }
? ? ? ?首先就是獲取當前的線程,然后根據(jù)當前線程來獲取一個ThreadLocalMap,如果map不為null,就往map中插入指定值,注意這的key是ThreadLocal實例;如果map為null,就創(chuàng)建一個map??纯吹?行getMap(t)做了啥: 1 //=========ThreadLocal=======源碼5.2
2 /**
3 * Get the map associated with a ThreadLocal.
4 * ......
5 */
6 ThreadLocalMap getMap(Thread t) {
7 return t.threadLocals;
8 }
9
10 /**
11 * ThreadLocalMap is a customized hash map suitable only for
12 * maintaining thread local values......
13 */
14 static class ThreadLocalMap {
15 ......
16 }
17
18 //==========Thread========
19 ThreadLocal.ThreadLocalMap threadLocals = null;
? ? ? ?getMap()返回的是指定線程(也就是當前線程)的threadLocals變量,這個變量是ThreadLocal.ThreadLocalMap類型的,而ThreadLocalMap是一個僅適用于維護線程本地變量值的自定義的HashMap。簡單來說,就是返回當前線程下的一個自定義HashMap。 ? ? ? ?下面我抽取了ThreadLocalMap的部分代碼,先來總體上認識它(這里我們不需要讀懂其中的每一行代碼,知道它里面主要做了哪些事就可以了):
1 //=========源碼5.3========
2 static class ThreadLocalMap {
3
4 static class Entry extends WeakReference<ThreadLocal<?>> {
5 /** The value associated with this ThreadLocal. */
6 Object value;
7
8 Entry(ThreadLocal<?> k, Object v) {
9 super(k);
10 value = v;
11 }
12 }
13
14 /**
15 * The initial capacity -- MUST be a power of two.
16 */
17 private static final int INITIAL_CAPACITY = 16;
18
19 /**
20 * The table, resized as necessary.
21 * table.length MUST always be a power of two.
22 */
23 private Entry[] table;
24
25 /**
26 * The number of entries in the table.
27 */
28 private int size = 0;
29
30 /**
31 * The next size value at which to resize.
32 */
33 private int threshold; // Default to 0
34
35 /**
36 * Set the resize threshold to maintain at worst a 2/3 load factor.
37 */
38 private void setThreshold(int len) {
39 threshold = len * 2 / 3;
40 }
41
42 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
43 table = new Entry[INITIAL_CAPACITY];
44 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
45 table[i] = new Entry(firstKey, firstValue);
46 size = 1;
47 setThreshold(INITIAL_CAPACITY);
48 }
49
50 /**
51 * Get the entry associated with key.
52 * ......
53 */
54 private Entry getEntry(ThreadLocal<?> key) {
55 int i = key.threadLocalHashCode & (table.length - 1);
56 Entry e = table[i];
57 if (e != null && e.get() == key)
58 return e;
59 else
60 return getEntryAfterMiss(key, i, e);
61 }
62
63 /**
64 * Set the value associated with key.
65 * ......
66 */
67 private void set(ThreadLocal<?> key, Object value) {
68
69 // We don't use a fast path as with get() because it is at
70 // least as common to use set() to create new entries as
71 // it is to replace existing ones, in which case, a fast
72 // path would fail more often than not.
73
74 Entry[] tab = table;
75 int len = tab.length;
76 int i = key.threadLocalHashCode & (len-1);
77
78 for (Entry e = tab[i];
79 e != null;
80 e = tab[i = nextIndex(i, len)]) {
81 ThreadLocal<?> k = e.get();
82
83 if (k == key) {
84 e.value = value;
85 return;
86 }
87
88 if (k == null) {
89 replaceStaleEntry(key, value, i);
90 return;
91 }
92 }
93
94 tab[i] = new Entry(key, value);
95 int sz = size;
96 if (!cleanSomeSlots(i, sz) && sz >= threshold)
97 rehash();
98 }
99
100 /**
101 * Remove the entry for key.
102 */
103 private void remove(ThreadLocal<?> key) {
104 Entry[] tab = table;
105 int len = tab.length;
106 int i = key.threadLocalHashCode & (len-1);
107 for (Entry e = tab[i];
108 e != null;
109 e = tab[i = nextIndex(i, len)]) {
110 if (e.get() == key) {
111 e.clear();
112 expungeStaleEntry(i);
113 return;
114 }
115 }
116 }
117
118 /**
119 * Double the capacity of the table.
120 */
121 private void resize() {
122 ......
123 }
124 }
View Code
? ? ? ?這里面維護了一個Entry[] table數(shù)組,初始容量為16,當數(shù)據(jù)超過當前容量的2/3時,就開始擴容,容量增大一倍。每一個Entry的K為ThreadLocal對象,V為要存儲的值。每一個Entry在數(shù)組中的位置,是根據(jù)其K(即ThreadLocal對象)的hashCode & (len - 1)來確定,如第44行所示,這里K的hashCode是系統(tǒng)給出的一個算法計算得到的。如果碰到K的hashCode值相同,即hash碰撞的場景,會采用尾插法形成鏈表。當對這個map進行set,get,remove操作的時候,也是通過K的hashCode來確定該Entry在table中的位置的,采用hashCode來查找數(shù)據(jù),效率比較高。這也是HashMap底層實現(xiàn)的基本原理,如果研究過HashMap源碼,這段代碼就應該比較容易理解了。 ? ? ? ?繼續(xù)看源碼5.1,第一次調用的時候,顯然map應該是null,就要執(zhí)行第8行createMap了, 1 //==========ThreadLocal=========源碼5.4
2 void createMap(Thread t, T firstValue) {
3 t.threadLocals = new ThreadLocalMap(this, firstValue);
4 }
? ? ? ?結合ThreadLocalMap源碼第41行的構造方法,就清楚了這個方法創(chuàng)建了一個ThreadLocalMap對象,并存儲了一個Entry<當前的ThreadLocal對象,value>。此時,在當前的線程下?lián)碛辛艘粋€ThreadLocalMap,這個ThreadLocalMap中維護了一個容量為16的table,table中存儲了一個以當前的ThreadLocal對象為K,value值為V的Entry。Thread、ThreadLocalMap、ThreadLocal、Entry之間的關系可以表示為下圖: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖5.1 ? ? ? ?而如果當前Thread的map已經存在了,源碼5.1就會執(zhí)行第6行了,進而執(zhí)行ThreadLocalMap中的set方法。結合前面對ThreadLocalMap的介紹,想必這個set方法也容易理解了,大致過程是: ? ? 1)根據(jù)Thread找到map; ? ? 2)通過傳入的this(即ThreadLocal對象),得到hashCode; ? ? 3)根據(jù)hashCode & (len - 1)確定對應Entry在table中的位置; ? ? 4)如果該Entry存在,則替換Value,否則新建(ThreadLocalMap源碼第78~92行表示在具有相同hashCode的Entry鏈表上找到對應的Entry,這和hash碰撞有關)。 ? ? ? ? ?在調用ThreadLocal的get方法時又做了什么呢?看看其源碼: 1 //=========ThreadLocal======源碼5.5
2 public T get() {
3 Thread t = Thread.currentThread();
4 ThreadLocalMap map = getMap(t);
5 if (map != null) {
6 ThreadLocalMap.Entry e = map.getEntry(this);
7 if (e != null) {
8 @SuppressWarnings("unchecked")
9 T result = (T)e.value;
10 return result;
11 }
12 }
13 return setInitialValue();
14 }
? ? ? ?現(xiàn)在,第12行及以前的代碼應該很容易理解了,結合ThreadLocalMap中的get源碼,我們再梳理一下: ? ? 1)根據(jù)Thread找到自己的map; ? ? 2)在map中通過this(即ThreadLocal對象)得到hashCode; ? ? 3)通過hashCode & (len-1)找到對應Entry在table中的位置; ? ? 4)返回Entry的value。 ? ? ? ?而如果map為null,或者在map中找到的Entry為null,那么就執(zhí)行第20行了。 1 //==========ThreadLocal========源碼5.6
2 private T setInitialValue() {
3 T value = initialValue();
4 Thread t = Thread.currentThread();
5 ThreadLocalMap map = getMap(t);
6 if (map != null)
7 map.set(this, value);
8 else
9 createMap(t, value);
10 return value;
11 }
12
13 protected T initialValue() {
14 return null;
15 }
第13行的initialValue()方法,前面介紹過,可以讓子類重寫,即給ThreadLocal指定初始值;如果沒有重寫,那返回值就是null。第4~9行前面也介紹過了,使用或者創(chuàng)建map來存入該值。 最后還一個remove()方法 1 //======ThreadLocal======
2 public void remove() {
3 ThreadLocalMap m = getMap(Thread.currentThread());
4 if (m != null)
5 m.remove(this);
6 }
結合ThrealLocalMap中的remove方法,完成對ThreadLocal值的刪除。其大致流程為: ? ? 1)根據(jù)當前Thread找到其map; ? ? 2)根據(jù)ThreadLocal對象得到hashCode; ? ? 3)通過hashCode & (len -1)找到在table中的位置; ? ? 4)在table中查找對應的Entry,如果存在則刪除。 ? 總結: ? ? ? ?通過對提供的4個接口方法的分析,我們應該就能清楚了,ThreadLocal之所以能夠為每一個線程維護一個副本,是因為每個線程都擁有一個map,這個map就是每個線程的專屬空間。也就是存在下面的關系圖(不用懷疑,該圖和圖5.1相比,只是少了容量大小):
結合這一節(jié)對ThreadLocal機制的介紹,實例3.1執(zhí)行后的就存在如下的數(shù)據(jù)結構了: ? ? 6、ThreadLocal在Looper中的使用 ? ? ? ?ThreadLocal在系統(tǒng)源碼中有很多地方使用,最典型的地方就是Handler的Looper中了。這里結合Looper中的源碼,來了解一下ThreadLocal在系統(tǒng)源碼中的使用。 ? ? ? ?我們知道,在一個App進程啟動的時候,會在ActiivtyThread類的main方法,也就是App的入口方法中,會為主線程準備一個Looper,如下代碼所示: 1 //======ActivityTread======源碼6.1
2 public static void main(String[] args) {
3 ......
4 Looper.prepareMainLooper();
5 ......
6 }
而在子線程中實例Handler的時候,總是需要顯示調用Looper.prepare()方法來為當前線程生成一個Looper對象,以及通過Looper.myLooper()來得到自己線程的Looper來傳遞給Handler。 Looper中相關的關鍵源碼如下: 1 //==========Looper========源碼6.2
2
3 // sThreadLocal.get() will return null unless you've called prepare().
4 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
5 private static Looper sMainLooper;
6
7 /**
8 * Initialize the current thread as a looper, marking it as an
9 * application's main looper. The main looper for your application
10 * is created by the Android environment, so you should never need
11 * to call this function yourself. See also: {@link #prepare()}
12 */
13 public static void prepareMainLooper() {
14 prepare(false);
15 synchronized (Looper.class) {
16 if (sMainLooper != null) {
17 throw new IllegalStateException("The main Looper has already been prepared.");
18 }
19 sMainLooper = myLooper();
20 }
21 }
22
23 /**
24 * Return the Looper object associated with the current thread. Returns
25 * null if the calling thread is not associated with a Looper.
26 */
27 public static @Nullable Looper myLooper() {
28 return sThreadLocal.get();
29 }
30
31 /** Initialize the current thread as a looper.
32 * ......
33 */
34 public static void prepare() {
35 prepare(true);
36 }
37 private static void prepare(boolean quitAllowed) {
38 if (sThreadLocal.get() != null) {
39 throw new RuntimeException("Only one Looper may be created per thread");
40 }
41 sThreadLocal.set(new Looper(quitAllowed));
42 }
43
44 /**
45 * Returns the application's main looper, which lives in the main thread of the application.
46 */
47 public static Looper getMainLooper() {
48 synchronized (Looper.class) {
49 return sMainLooper;
50 }
51 }
我們可以看到不少ThreadLocal的影子,Looper也正是通過ThreadLocal來為每個線程維護一份Looper實例的。通過我們前文的介紹,這里應該能夠輕而易舉理解其中的運作機制了吧,這里就再不啰嗦了。 ? 7、實踐是檢驗真理的唯一標準 ? ? ? ? 前面介紹了ThreadLocal提供的四個接口,以及詳細講解了它的工作原理?,F(xiàn)在我們將實例3.1做一些修改,將各個接口的功能都包含進來,并稍微增加一點復雜度,如果能夠看懂這個實例,就算是真的理解ThreadLocal了。 1 //=========實例7.1=======
2 private ThreadLocal<String> mStrThreadLocal = new ThreadLocal<String>() {
3 @Override
4 protected String initialValue() {
5 Log.i("threadlocaldemo", "initialValue");
6 return "initName";
7 }
8 };
9 private ThreadLocal<Long> mLongThreadLocal = new ThreadLocal<>();
10 private void testThreadLocal() throws InterruptedException {
11 mStrThreadLocal.set("main-thread");
12 mLongThreadLocal.set(Thread.currentThread().getId());
13 Log.i("threadlocaldemo", "result-1:name=" mStrThreadLocal.get() ";id=" mLongThreadLocal.get());
14 Thread thread_1 = new Thread() {
15 @Override
16 public void run() {
17 super.run();
18 mStrThreadLocal.set("thread_1");
19 mLongThreadLocal.set(Thread.currentThread().getId());
20 Log.i("threadlocaldemo", "result-2:name=" mStrThreadLocal.get() ";id=" mLongThreadLocal.get());
21 }
22 };
23 thread_1.start();
24 //該句表示thread_1執(zhí)行完后才會繼續(xù)執(zhí)行
25 thread_1.join();
26 Thread thread_2 = new Thread() {
27 @Override
28 public void run() {
29 super.run();
30 Log.i("threadlocaldemo", "result-3:name=" mStrThreadLocal.get() ";id=" mLongThreadLocal.get());
31 }
32 };
33 thread_2.start();
34 //該句表示thread_2執(zhí)行完后才會繼續(xù)執(zhí)行
35 thread_2.join();
36 mStrThreadLocal.remove();
37 Log.i("threadlocaldemo", "result-4:name=" mStrThreadLocal.get() ";id=" mLongThreadLocal.get());
38 }
在主線程中運行該方法,執(zhí)行結果為: 1 12-14 16:25:40.662 4844-4844/com.example.demos I/threadlocaldemo: result-1:name=main-thread;id=2 2 12-14 16:25:40.668 4844-5351/com.example.demos I/threadlocaldemo: result-2:name=thread_1;id=926 3 12-14 16:25:40.669 4844-5353/com.example.demos I/threadlocaldemo: initialValue 4 12-14 16:25:40.669 4844-5353/com.example.demos I/threadlocaldemo: result-3:name=initName;id=null 5 12-14 16:25:40.669 4844-4844/com.example.demos I/threadlocaldemo: initialValue 6 12-14 16:25:40.669 4844-4844/com.example.demos I/threadlocaldemo: result-4:name=initName;id=2 此時存在的數(shù)據(jù)結構為:
? ? ? ?對于這份log和數(shù)據(jù)結構圖,這里就不再一一講解了,如果前面都看懂了,這些都是小菜一碟。 ? 結語 ? ? ? ?對ThreadLocal的講解這里就結束了,能讀到這里,也足以說明你是人才,一定前途無量,祝你好運,早日走上人生巔峰! ? ? ? ?由于經驗和水平有限,有描述不當或不準確的地方,還請不吝賜教,謝謝! ? 來源:https://www./content-4-597051.html |
|
|