Java-ThreadLocal三种使用场景
關于ThreadLocal
JDK1.2的版本中就提供java.lang.ThreadLocal類,每一個ThreadLocal能夠放一個線程級別的變量, 它本身能夠被多個線程共享使用,并且又能夠達到線程安全的目的,且絕對線程安全。
ThreadLocal包含了四個方法:
void set(Object value)設置當前線程的線程局部變量的值。 public Object get()該方法返回當前線程所對應的線程局部變量。 public void remove()將當前線程局部變量的值刪除,其目的是為了減少內存使用,加快內存回收。 protected Object initialValue()返回該線程局部變量的初始值,該方法是一個protected的方法,目的是為了讓子類覆蓋而設計的。在實際的工作中,我們在哪些場景下會用到ThreadLocal呢?這里筆者整理了三個使用場景。
場景一:代替參數的顯式傳遞
當我們在寫API接口的時候,通常Controller層會接受來自前端的入參,當這個接口功能比較復雜的時候,可能我們調用的Service層內部還調用了 很多其他的很多方法,通常情況下,我們會在每個調用的方法上加上需要傳遞的參數。
但是如果我們將參數存入ThreadLocal中,那么就不用顯式的傳遞參數了,而是只需要ThreadLocal中獲取即可。
這個場景其實使用的比較少,一方面顯式傳參比較容易理解,另一方面我們可以將多個參數封裝為對象去傳遞。
場景二:全局存儲用戶信息
在現在的系統設計中,前后端分離已基本成為常態,分離之后如何獲取用戶信息就成了一件麻煩事,通常在用戶登錄后, 用戶信息會保存在Session或者Token中。這個時候,我們如果使用常規的手段去獲取用戶信息會很費勁,拿Session來說,我們要在接口參數中加上HttpServletRequest對象,然后調用 getSession方法,且每一個需要用戶信息的接口都要加上這個參數,才能獲取Session,這樣實現就很麻煩了。
在實際的系統設計中,我們肯定不會采用上面所說的這種方式,而是使用ThreadLocal,我們會選擇在攔截器的業務中, 獲取到保存的用戶信息,然后存入ThreadLocal,那么當前線程在任何地方如果需要拿到用戶信息都可以使用ThreadLocal的get()方法 (異步程序中ThreadLocal是不可靠的)
對于筆者而言,這個場景使用的比較多,當用戶登錄后,會將用戶信息存入Token中返回前端,當用戶調用需要授權的接口時,需要在header中攜帶 Token,然后攔截器中解析Token,獲取用戶信息,調用自定義的類(AuthNHolder)存入ThreadLocal中,當請求結束的時候,將ThreadLocal存儲數據清空, 中間的過程無需在關注如何獲取用戶信息,只需要使用工具類的get方法即可。
public class AuthNHolder {private static final ThreadLocal<Map<String,String>> loginThreadLocal = new ThreadLocal<Map<String,String>>();public static void map(Map<String,String> map){loginThreadLocal.set(map);}public static String userId(){return get("userId");}public static String get(String key){Map<String,String> map = getMap();return map.get(key);}public static void clear(){loginThreadLocal.remove();}}場景三:解決線程安全問題
在Spring的Web項目中,我們通常會將業務分為Controller層,Service層,Dao層, 我們都知道@Autowired注解默認使用單例模式,那么不同請求線程進來之后,由于Dao層使用單例,那么負責數據庫連接的Connection也只有一個, 如果每個請求線程都去連接數據庫,那么就會造成線程不安全的問題,Spring是如何解決這個問題的呢?
在Spring項目中Dao層中裝配的Connection肯定是線程安全的,其解決方案就是采用ThreadLocal方法,當每個請求線程使用Connection的時候, 都會從ThreadLocal獲取一次,如果為null,說明沒有進行過數據庫連接,連接后存入ThreadLocal中,如此一來,每一個請求線程都保存有一份 自己的Connection。于是便解決了線程安全問題
ThreadLocal在設計之初就是為解決并發問題而提供一種方案,每個線程維護一份自己的數據,達到線程隔離的效果。
慎用的場景
1.線程池中線程調用使用ThreadLocal 由于線程池中對線程管理都是採用線程復用的方法。在線程池中線程非常難結束甚至于永遠不會結束。這將意味著線程持續的時間將不可預測,甚至與JVM的生命周期一致
2.異步程序中,ThreadLocal的參數傳遞是不靠譜的, 由于線程將請求發送后。就不再等待遠程返回結果繼續向下運行了,真正的返回結果得到后,處理的線程可能是其他的線程。Java8中的并發流也要考慮這種情況
3.使用完ThreadLocal ,最好手動調用 remove() 方法,防止出現內存溢出,因為中使用的key為ThreadLocal的弱引用, 如果ThreadLocal 沒有被外部強引用的情況下,在垃圾回收的時候會被清理掉的,但是如果value是強引用,不會被清理, 這樣一來就會出現 key 為 null 的 value。
總結
以上是生活随笔為你收集整理的Java-ThreadLocal三种使用场景的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java中的ThreadLocal详解
- 下一篇: Spring Cloud Eureka