ThreadLoacl,InheritableThreadLocal,原理,以及配合线程池使用的一些坑
雖然使用AOP可以獲取方法簽名,但是如果要獲取方法中計(jì)算得出的數(shù)據(jù),那么就得使用ThreadLocal,如果還涉及父線(xiàn)程,那么可以選擇InheritableThreadLocal.
注意:理解一些原理能夠減少很多不可控問(wèn)題,最簡(jiǎn)單的使用方式就是不要交給線(xiàn)程池處理.為了提高一點(diǎn)性能,而導(dǎo)致數(shù)據(jù)錯(cuò)誤得不償失.
2018年4月12日 12:44:41更新 關(guān)于InheritableThreadLocal?配合線(xiàn)程池的問(wèn)題解決方案 ->?TransmittableThreadLocal 解決 線(xiàn)程池線(xiàn)程復(fù)用 無(wú)法復(fù)制 InheritableThreadLocal 的問(wèn)題.
首先看看ThreadLoacl如何做到共享變量實(shí)現(xiàn)為線(xiàn)程私有變量
Thread源碼里面,有一個(gè)ThreadLoaclMap
ThreadLocal.ThreadLocalMap threadLocals = null;ThreadLoacl set方法源碼
public void set(T value) {//獲取當(dāng)前線(xiàn)程Thread t = Thread.currentThread();//獲取當(dāng)前線(xiàn)程ThreadLoaclMapThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}ThreadLoacl getMap方法源碼
ThreadLocalMap getMap(Thread t) {return t.threadLocals;}測(cè)試TreadLocal線(xiàn)程私有
?
public class A {static final ThreadLocal<String> threadParam = new ThreadLocal<>();public static void main(String[] args) throws InterruptedException {//死循環(huán),測(cè)多幾次看結(jié)果while (true) {//線(xiàn)程1new Thread(() -> {//設(shè)置參數(shù)threadParam.set("abc");//輸出參數(shù)System.out.println("t1:" + threadParam.get());//看起來(lái)像是多余操作 // threadParam.remove();}).start();TimeUnit.SECONDS.sleep(1);new Thread(() -> {//線(xiàn)程二,測(cè)試是否能獲取abcSystem.out.println("t2:" + threadParam.get());}).start();}} }?
?
測(cè)試結(jié)果
線(xiàn)程1永遠(yuǎn)輸出abc
線(xiàn)程2永遠(yuǎn)輸出null
看起來(lái)很美好.但是也有需要注意的地方
如果使用線(xiàn)程池,以下把線(xiàn)程交給線(xiàn)程池處理
?
/*** * @author ZhenWeiLai**/ public class B {static final ThreadLocal<String> threadParam = new ThreadLocal<>();public static void main(String[] args) throws InterruptedException {//固定池內(nèi)只有存活3個(gè)線(xiàn)程ExecutorService execService = Executors.newFixedThreadPool(3);//死循環(huán)幾次才能看出效果while (true) {Thread t = new Thread(()->{threadParam.set("abc");System.out.println("t1:" + threadParam.get());//如果不調(diào)用remove,將引發(fā)問(wèn)題 // threadParam.remove();});execService.execute(t);TimeUnit.SECONDS.sleep(1);Thread t2 = new Thread(()-> {System.out.println("t2:" + threadParam.get());});execService.execute(t2);}} }?
測(cè)試結(jié)果:
t1:abc
 t1:abc
 t2:null
 t2:abc ?//因復(fù)用線(xiàn)程而導(dǎo)致問(wèn)題
 t1:abc
原因:線(xiàn)程池把線(xiàn)程提交到隊(duì)列,當(dāng)被調(diào)用的時(shí)候如果存在空閑線(xiàn)程就直接復(fù)用線(xiàn)程,僅僅是調(diào)用了用戶(hù)提交的run方法.
所以當(dāng)ThreadLocal參數(shù)使用完,記得調(diào)用remove方法
除了ThreadLocal 還有?InheritableThreadLocal,子線(xiàn)程可以共享父線(xiàn)程的InheritableThreadLocal
?
InheritableThreadLocal實(shí)現(xiàn)的關(guān)鍵源碼
//初始化一個(gè)線(xiàn)程時(shí),獲取當(dāng)前線(xiàn)程,作為父線(xiàn)程Thread parent = currentThread(); //如果父線(xiàn)程inheritableThreadLocals 不為空時(shí),子線(xiàn)程復(fù)制一份inheritableThreadLocals if (parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);測(cè)試代碼
/*** * @author ZhenWeiLai**/ public class A {static final InheritableThreadLocal<String> threadParam = new InheritableThreadLocal<>();public static void main(String[] args) throws InterruptedException {//死循環(huán),測(cè)多幾次看結(jié)果while (true) {//線(xiàn)程1,測(cè)試是否能獲取父線(xiàn)程參數(shù)new Thread(() -> {//設(shè)置參數(shù)threadParam.set("abc");//輸出參數(shù)System.out.println("t1:" + threadParam.get());//線(xiàn)程2,測(cè)試是否能獲取線(xiàn)程1參數(shù)new Thread(() -> {System.out.println("t2:" + threadParam.get());//為了測(cè)試線(xiàn)程三能否獲得,這里先不刪除 // threadParam.remove();}).start();}).start();TimeUnit.SECONDS.sleep(1);//線(xiàn)程3,測(cè)試是否能獲取線(xiàn)程1參數(shù)new Thread(() -> {System.out.println("t3:" + threadParam.get());}).start();}} }?輸出結(jié)果:自線(xiàn)程可以獲取參數(shù),非自線(xiàn)程不能獲取.
t1:abc
 t2:abc
 t1:abc
 t3:null
 t2:abc
 t3:null
 t1:abc
 t2:abc
 t3:null
 t1:abc
 t2:abc
再一次看似很美好,以下寫(xiě)一個(gè)復(fù)雜點(diǎn)的,交給線(xiàn)程池執(zhí)行
package thread.base.threadloacl;import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit;/*** * @author ZhenWeiLai**/ public class B {static final InheritableThreadLocal<String> threadParam = new InheritableThreadLocal<>();public static void main(String[] args) throws InterruptedException {//固定池內(nèi)只有存活3個(gè)線(xiàn)程ExecutorService execService = Executors.newFixedThreadPool(3);//死循環(huán)幾次才能看出效果while (true) {//線(xiàn)程1,里面有兩個(gè)子線(xiàn)程Thread t = new Thread(()->{threadParam.set("abc");System.out.println("t1:" + threadParam.get());Thread t2 = new Thread(()->{System.out.println("t2:" + threadParam.get()); // threadParam.remove();});execService.execute(t2);Thread t3 = new Thread(()->{System.out.println("t3:" + threadParam.get()); // threadParam.remove();});execService.execute(t3);});execService.execute(t);TimeUnit.SECONDS.sleep(1);//線(xiàn)程4,線(xiàn)程1同級(jí)Thread t4 = new Thread(()-> {threadParam.set("CBA");System.out.println("t4:" + threadParam.get());});execService.execute(t4);}} }輸出結(jié)果:
t1:abc
 t2:abc
 t3:abc
 t4:CBA
 t1:abc
 t2:abc
 t3:abc
 t4:CBA
 t1:abc
 t2:abc
 t3:CBA?//因復(fù)用線(xiàn)程而導(dǎo)致問(wèn)題
 t4:CBA
Runnable只是線(xiàn)程方法,Thread才是線(xiàn)程,需要給Runnable加上一個(gè)線(xiàn)程的殼,調(diào)用start才會(huì)使用線(xiàn)程執(zhí)行.
這里線(xiàn)程池只存活3個(gè)線(xiàn)程,那么在線(xiàn)程池復(fù)用線(xiàn)程(殼)的時(shí)候,殼的屬性只有在創(chuàng)建的時(shí)候才會(huì)被重新設(shè)置值(如果有操作的話(huà),例如:InheritableThreadLocal,ThreadLocal).
這些殼被創(chuàng)建好以后提交給了線(xiàn)程池,但是線(xiàn)程方法并沒(méi)有馬上執(zhí)行,然后被其他殼修改了屬性.當(dāng)這個(gè)線(xiàn)程方法開(kāi)始執(zhí)行的時(shí)候,已經(jīng)不是自己創(chuàng)建的殼了
這里線(xiàn)程3,因?yàn)橛捎诰€(xiàn)程切換使用了被線(xiàn)程4修改以后的殼的屬性.
?
加大線(xiàn)程池,以滿(mǎn)足每個(gè)線(xiàn)程方法獨(dú)立使用一個(gè)線(xiàn)程只能保證第一次運(yùn)行正確,因?yàn)闆](méi)有涉及Thread重用的問(wèn)題.但是如果涉及重用Thread(殼)的時(shí)候,沒(méi)有辦法可以保證.
總結(jié)
以上是生活随笔為你收集整理的ThreadLoacl,InheritableThreadLocal,原理,以及配合线程池使用的一些坑的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
 
                            
                        - 上一篇: ThreadLocal父子线程传递实现方
- 下一篇: 从fastjson的TypeRefere
