在分析ThreadLocal之前,首先我們提出三個(gè)問題,后續(xù)會(huì)圍繞這三個(gè)問題解析ThreadLocal的原理。
- 什么是ThreadLocal?
- ThreadLocal怎么用?
- ThreadLocal的原理是什么?為什么能保證每個(gè)線程的數(shù)據(jù)都不受其他線程干擾?
1.什么是ThreadLocal?
- 首先拋個(gè)定義,ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路。使用這個(gè)工具類可以很簡(jiǎn)潔地編寫出優(yōu)美的多線程程序,ThreadLocal并不是一個(gè)Thread,而是Thread的局部變量。
- 翻譯成能聽懂的話就是可以通過ThreadLocal保證同一線程的某些數(shù)據(jù)不被其他線程干擾,影響。
2.ThreadLocal怎么用?
ThreadLocal的使用及其簡(jiǎn)單,常用的方法只有兩個(gè),get(),set();
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set(TAG);
threadLocal.get();
舉個(gè)使用ThreadLocal的真實(shí)場(chǎng)景
- ? ? ? ?2.1.我們正常的項(xiàng)目中有很多復(fù)雜的邏輯,首先我們會(huì)分割不同的業(yè)務(wù),并單獨(dú)開啟多個(gè)線程去執(zhí)行對(duì)應(yīng)的業(yè)務(wù)。
- ? ? ? ?2.2. 每個(gè)業(yè)務(wù)需要打印本業(yè)務(wù)的全量日志。正常可通過Log.d(TAG,Message)來(lái)打印;每條的日志的TAG都需要一條條的設(shè)置。
- ? ? ? ?2.3. 其實(shí)該場(chǎng)景可以使用ThreadLocal.set(TAG),在Log工具類中使用Log.d(ThreadLoacl.get(),Message)即可,省去了頻繁設(shè)置TAG的操作。
3. ThreadLocal的原理是什么?為什么能保證每個(gè)線程的數(shù)據(jù)都不受其他線程干擾?
- 原理可以理解為每一個(gè)線程類內(nèi)都有一個(gè)ThreadLocalMap類型的局部變量,該變量中設(shè)有一個(gè)ThreadLocalMap.Entry[]數(shù)組,數(shù)組中的每一個(gè)ThreadLocalMap.Entry對(duì)象中存的是k/v(ThreadLocal/Value)鍵值對(duì),而我們存儲(chǔ)的數(shù)據(jù)就在這個(gè)鍵值對(duì)中。
- 正常的使用中主要分為3步
- new ThreadLocal<>();
- threadLocal.set(Object);
- threadLocal.get();
- 下文主要從這三步進(jìn)行展開分析。
1. ThreadLocal<String> threadlocal = new ThreadLocal<>();
- 該操作調(diào)用了ThreadLocal的構(gòu)造函數(shù),除了將ThreadLocal內(nèi)部屬性threadlocalHashcode自增了0x61c88647外,并沒有做其他的操作。
2. 當(dāng)調(diào)用ThreadLocal.set(Object),該操作中主要涉及了4個(gè)步驟。
1. Thread t = Thread.currentThread();//獲取當(dāng)前線程
2. ThreadLocalMap map = ThreadLocal.getMap(t);//獲取當(dāng)前線程中的ThreadLocalMap對(duì)象。
3. map!=null 則調(diào)用 map.set(ThreadLocal,Value);
獲取ThreadLocalMap內(nèi)部Entry[]數(shù)組,通過ThreadLocal.threadLocalHashCode & (len-1)計(jì)算將要存放的位置索引。
循環(huán)向后判斷沖突,如果Entry[i]存在數(shù)據(jù),且key和將要存儲(chǔ)的數(shù)據(jù)相同,則替換value,如果key為null則認(rèn)為當(dāng)前數(shù)據(jù)是臟數(shù)據(jù),啟動(dòng)環(huán)形清理。
沒有沖突則 new ThreadLocalMap.Entry(key, value)添加到Entry[i]并啟動(dòng)清理,如果清理不成功并且Entry.len>len*0.75則啟動(dòng)擴(kuò)容rehash(),擴(kuò)大到原來(lái)大小的2倍。
4. map == null 則調(diào)用 ThreadLocal.createMap(Thread,Value);
Thread.threadLocalMap = new ThreadLocalMap(ThreadLocal,Value);
ThreadLocalMap.Entry[] entry = new ThreadLocalMap.Entry[16]//初始化Entry[]數(shù)組。
通過 i = ThreadLocal.threadLocalHashCode & (len - 1)計(jì)算在ThreadLocalMap.Entry[]數(shù)組中的存儲(chǔ)索引。
ThreadLocalMap.Entry[i] = new ThreadLocalMap.Entry(k,v);
3. 當(dāng)調(diào)用ThreadLocal.get(),該操作中也主要涉及了4個(gè)步驟
1. Thread t = Thread.currentThread();
2. ThreadLocalMap map = t.threadLocals;//獲取當(dāng)前線程中的ThreadLocalMap對(duì)象。
3. map != null map.getEntry(ThreadLocal);
通過 i = ThreadLocal.threadLocalHashCode & (len - 1)計(jì)算當(dāng)前ThreadLocal數(shù)據(jù)存儲(chǔ)的位置。
Entry e = ThreadLocalMap.Entry[i] ,e!=null且e.get()和當(dāng)前ThreadLocal key相等則返回 e,否則循環(huán)后續(xù)的數(shù)據(jù).
如有key相同的則返回,如有臟數(shù)據(jù)(key==null的數(shù)據(jù)為臟數(shù)據(jù))則清理。
4. map == null ThreadLocal.setInitialValue()
t.threadLocals = new ThreadLocalMap(ThreadLocal, null);
ThreadLocalMap.Entry[] entry = new ThreadLocalMap.Entry[16]//初始化Entry[]數(shù)組。
通過 i = ThreadLocal.threadLocalHashCode & (len - 1)計(jì)算在ThreadLocalMap.Entry[]數(shù)組中的存儲(chǔ)索引。
ThreadLocalMap.Entry[i] = new ThreadLocalMap.Entry(k,null);
總結(jié)
- 每一個(gè)Thread 都維護(hù)了一個(gè)類型為ThreadLocalMap名叫threadLocals的對(duì)象,每次get獲取的時(shí),均通過Thread.currentThread()獲取了當(dāng)前線程的ThreadLocalMap,因此做到了不同線程之間互不干擾。
- 當(dāng)調(diào)用ThreadLocal.set()方法時(shí),會(huì)通過當(dāng)前線程獲取到線程中的ThreadLocalMap對(duì)象。
然后獲取到ThreadLocalMap.Entry[]數(shù)組,最后通過hash計(jì)算出數(shù)組中的存放位置,然后new ThreadLocalMap.Entry(ThreadLocal<?> k, Object v)存放在數(shù)組內(nèi),該索引下有重復(fù)key則替換,key==null則啟動(dòng)環(huán)形清理,沒數(shù)據(jù)則插入。
- 當(dāng)調(diào)用ThreadLocal.set()方法時(shí),會(huì)通過當(dāng)前線程獲取到線程中的ThreadLocalMap對(duì)象。
- 當(dāng)調(diào)用ThreadLocal.get()方法時(shí),ThreadLocal會(huì)通過線程獲取當(dāng)前線程中的ThreadLocalMap對(duì)象.
然后獲取到ThreadLocalMap.Entry[]數(shù)組,并通過hash計(jì)算出數(shù)組中的存放位置.
由ThreadLocalMap.Entry[i]獲取到數(shù)據(jù),判斷key相同則返回,key == null則清理。
如果獲取ThreadLocalMap == null,則啟動(dòng)初始化,通過ThreadLocalMap創(chuàng)建長(zhǎng)度為16的ThreadLocalMap.Entry[],并存value 為 null的一條數(shù)據(jù)到Entry[]數(shù)組下。
- 當(dāng)調(diào)用ThreadLocal.get()方法時(shí),ThreadLocal會(huì)通過線程獲取當(dāng)前線程中的ThreadLocalMap對(duì)象.