日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ThreadLoacl,InheritableThreadLocal,原理,以及配合线程池使用的一些坑

發布時間:2024/1/17 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ThreadLoacl,InheritableThreadLocal,原理,以及配合线程池使用的一些坑 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

雖然使用AOP可以獲取方法簽名,但是如果要獲取方法中計算得出的數據,那么就得使用ThreadLocal,如果還涉及父線程,那么可以選擇InheritableThreadLocal.

注意:理解一些原理能夠減少很多不可控問題,最簡單的使用方式就是不要交給線程池處理.為了提高一點性能,而導致數據錯誤得不償失.

2018年4月12日 12:44:41更新 關于InheritableThreadLocal?配合線程池的問題解決方案 ->?TransmittableThreadLocal 解決 線程池線程復用 無法復制 InheritableThreadLocal 的問題.

首先看看ThreadLoacl如何做到共享變量實現為線程私有變量

Thread源碼里面,有一個ThreadLoaclMap

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLoacl set方法源碼

public void set(T value) {//獲取當前線程Thread t = Thread.currentThread();//獲取當前線程ThreadLoaclMapThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}

ThreadLoacl getMap方法源碼

ThreadLocalMap getMap(Thread t) {return t.threadLocals;}

測試TreadLocal線程私有

?

public class A {static final ThreadLocal<String> threadParam = new ThreadLocal<>();public static void main(String[] args) throws InterruptedException {//死循環,測多幾次看結果while (true) {//線程1new Thread(() -> {//設置參數threadParam.set("abc");//輸出參數System.out.println("t1:" + threadParam.get());//看起來像是多余操作 // threadParam.remove();}).start();TimeUnit.SECONDS.sleep(1);new Thread(() -> {//線程二,測試是否能獲取abcSystem.out.println("t2:" + threadParam.get());}).start();}} }

?

?

測試結果

線程1永遠輸出abc

線程2永遠輸出null

看起來很美好.但是也有需要注意的地方

如果使用線程池,以下把線程交給線程池處理

?

/*** * @author ZhenWeiLai**/ public class B {static final ThreadLocal<String> threadParam = new ThreadLocal<>();public static void main(String[] args) throws InterruptedException {//固定池內只有存活3個線程ExecutorService execService = Executors.newFixedThreadPool(3);//死循環幾次才能看出效果while (true) {Thread t = new Thread(()->{threadParam.set("abc");System.out.println("t1:" + threadParam.get());//如果不調用remove,將引發問題 // threadParam.remove();});execService.execute(t);TimeUnit.SECONDS.sleep(1);Thread t2 = new Thread(()-> {System.out.println("t2:" + threadParam.get());});execService.execute(t2);}} }

?

測試結果:

t1:abc
t1:abc
t2:null
t2:abc ?//因復用線程而導致問題
t1:abc

原因:線程池把線程提交到隊列,當被調用的時候如果存在空閑線程就直接復用線程,僅僅是調用了用戶提交的run方法.

所以當ThreadLocal參數使用完,記得調用remove方法

除了ThreadLocal 還有?InheritableThreadLocal,子線程可以共享父線程的InheritableThreadLocal

?

InheritableThreadLocal實現的關鍵源碼

//初始化一個線程時,獲取當前線程,作為父線程Thread parent = currentThread(); //如果父線程inheritableThreadLocals 不為空時,子線程復制一份inheritableThreadLocals if (parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

測試代碼

/*** * @author ZhenWeiLai**/ public class A {static final InheritableThreadLocal<String> threadParam = new InheritableThreadLocal<>();public static void main(String[] args) throws InterruptedException {//死循環,測多幾次看結果while (true) {//線程1,測試是否能獲取父線程參數new Thread(() -> {//設置參數threadParam.set("abc");//輸出參數System.out.println("t1:" + threadParam.get());//線程2,測試是否能獲取線程1參數new Thread(() -> {System.out.println("t2:" + threadParam.get());//為了測試線程三能否獲得,這里先不刪除 // threadParam.remove();}).start();}).start();TimeUnit.SECONDS.sleep(1);//線程3,測試是否能獲取線程1參數new Thread(() -> {System.out.println("t3:" + threadParam.get());}).start();}} }

?輸出結果:自線程可以獲取參數,非自線程不能獲取.

t1:abc
t2:abc
t1:abc
t3:null
t2:abc
t3:null
t1:abc
t2:abc
t3:null
t1:abc
t2:abc

再一次看似很美好,以下寫一個復雜點的,交給線程池執行

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 {//固定池內只有存活3個線程ExecutorService execService = Executors.newFixedThreadPool(3);//死循環幾次才能看出效果while (true) {//線程1,里面有兩個子線程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);//線程4,線程1同級Thread t4 = new Thread(()-> {threadParam.set("CBA");System.out.println("t4:" + threadParam.get());});execService.execute(t4);}} }

輸出結果:

t1:abc
t2:abc
t3:abc
t4:CBA
t1:abc
t2:abc
t3:abc
t4:CBA
t1:abc
t2:abc
t3:CBA?//因復用線程而導致問題
t4:CBA

Runnable只是線程方法,Thread才是線程,需要給Runnable加上一個線程的殼,調用start才會使用線程執行.

這里線程池只存活3個線程,那么在線程池復用線程(殼)的時候,殼的屬性只有在創建的時候才會被重新設置值(如果有操作的話,例如:InheritableThreadLocal,ThreadLocal).

這些殼被創建好以后提交給了線程池,但是線程方法并沒有馬上執行,然后被其他殼修改了屬性.當這個線程方法開始執行的時候,已經不是自己創建的殼了

這里線程3,因為由于線程切換使用了被線程4修改以后的殼的屬性.

?

加大線程池,以滿足每個線程方法獨立使用一個線程只能保證第一次運行正確,因為沒有涉及Thread重用的問題.但是如果涉及重用Thread(殼)的時候,沒有辦法可以保證.

總結

以上是生活随笔為你收集整理的ThreadLoacl,InheritableThreadLocal,原理,以及配合线程池使用的一些坑的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。