Java | 多執行緒 | ThreadLocal結合線程池的正確使用方式
1)問題:
才發現,寫這篇部落格之前,自己一直在以一種錯誤的姿勢在用threadLocal
物件。
場景就是threadLocal
在專案中使用時,出現取值錯誤的情況。花了不少時間排查,最終還是排查到執行緒池上。之前一直沒有問題,或許是因為併發不高。最終今天還是遇到了問題(出來混,遲早是要還的)。
不禁開始懷疑:threadLocal
遇到執行緒池就不好用了?
2)分析:
我們都知道threadLocal中維護了一個執行緒和value的對映,當前執行緒的threadLocal
即為key,value為引用的物件。
t.threadLocals = new ThreadLocalMap(this, firstValue);
每個執行緒儲存一份,達到執行緒安全。
但是線上程複用的情況下,threadLocal
並不能保證按照預期執行,很有可能出現數據錯亂。原因就是執行緒池中的執行緒在還未銷燬的情況下,新的請求進來,會繼續複用執行緒池中的執行緒,而這些執行緒在之前處理的過程中,對應的threadLocal
有可能已經有值,導致出錯。程式碼如下:
/** * Created by zhangshukang on 2018/7/27. */ public class ThreadLocalTest { public static void main(String[] args) throws InterruptedException { ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); ExecutorService pool = new ThreadPoolExecutor( 1, 1, 50000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), new ThreadFactory() { private AtomicInteger number = new AtomicInteger(0); @Override public Thread newThread(Runnable runnable) { return new Thread(runnable, "admin" + "-" + number.getAndIncrement()); } }, new ThreadPoolExecutor.AbortPolicy()); pool.execute(()->{ System.out.println(Thread.currentThread().getName()); threadLocal.set(5); System.out.println(threadLocal.get()); }); Thread.sleep(3000l); System.out.println("-------------------"); pool.execute(()->{ System.out.println(Thread.currentThread().getName()); System.out.println(threadLocal.get()); }); } }
執行結果如下:
admin-0
5
-------------------
admin-0
5
這裡只是模擬伺服器執行緒池的執行流程,當web伺服器接收到一個請求處理結束,未清理掉threadLocal中的變數,此時另一個請求進來,同樣用該執行緒去處理。這個時候發現threadLocal中已有變數,如果使用不慎,會出現資料錯誤的情形。
可以看到上面的執行結果:執行緒 admin-0 複用,輸出了相同的變數。
3)正確姿勢:
正確姿勢是在threadLocal
變數使用之後,呼叫remove()
方法。這麼做也可以避免記憶體洩露,如果沒有呼叫該方法,那當map中當前執行緒被回收,對應的value得不到回收,容易引起記憶體洩露。
這樣又引出了新問題,就是我們怎麼區分在什麼地方進行remove()
呢?假如一個介面呼叫了remove()
,然後結束了程式碼邏輯,這沒問題。但是有可能另一個介面也執行完該程式碼塊remove()
,接著又在別的地方呼叫了get()
方法,這種場景還是會出錯。
所以儘量避免這種使用場景,要保證remove()
的程式碼塊的呼叫鏈後面,不會再執行get方法。
友鏈:探果網