您好,登錄后才能下訂單哦!
本篇內容介紹了“Java多線程ThreadLocal的知識點有哪些”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
ThreadLocal 的常見應用場景有兩種:
多線程并發場景中,用來保障線程安全。
處理較為復雜的業務時,使用ThreadLocal代替參數的顯示傳遞。
多線程訪問同一個共享變量的時候容易出現并發問題,特別是多個線程對一個變量進行寫入的時候,為了保證線程安全,一般使用者在訪問共享變量的時候需要進行額外的同步措施才能保證線程安全性,如:synchronized、Lock之類的鎖。
ThreadLocal是除了加鎖這種同步方式之外的一種,規避多線程訪問出現線程不安全的方法。當我們在創建一個變量后,如果每個線程對其進行訪問的時候訪問的都是線程自己的變量,這樣就不會存在線程不安全問題。
ThreadLocal是JDK包提供的,它提供線程本地變量,如果創建一個ThreadLocal變量,那么訪問這個變量的每個線程都會有這個變量的一個副本,在實際多線程操作的時候,操作的是自己本地內存中的變量,從而規避了線程安全問題。
這里舉幾個例子:
示例1:獲取接口的當前請求用戶
在后臺接口業務邏輯的全過程中,如果需要在多個地方獲取當前請求用戶的信息。通常的一種做法就是:在接口請求時,通過過濾器、攔截器、AOP等方式,從session或token中獲取當前用戶信息,存入ThreadLocal中。
在整個接口處理過程中,如果沒有另外創建線程,都可以直接從ThreadLocal變量中獲取當前用戶,而無需再從Session、token中驗證和獲取用戶。這種方案設計不僅提高性能,最重要的是將原本復雜的邏輯和代碼實現,變得簡潔明了。例如下面的這個例子:
(1)定義ThreadLocal變量:UserProfileThread.java
public class UserProfileThread {
private static ThreadLocal<UserProfile> USER_PROFILE_TL =new ThreadLocal<>();
public static void setUserProfile(UserProfile userProfile){
USER_PROFILE_TL.set(userProfile);
}
public static UserProfile getUserProfile() {
return USER_PROFILE_TL.get();
}
public static String getCurrentUser() {
return Optional.ofNullable(USER_PROFILE_TL.get())
.map(UserProfile::getUid)
.orElse(UserProfile.ANONYMOUS_USER);
}
}
(2)在過濾器中設置變量值:
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
UserProfile userProfile = null;
// ... 驗證和獲取用戶信息 userProfile
UserProfileThread.setUserProfile(userProfile);
filterChain.doFilter(servletRequest, servletResponse);
}
(3)獲取當前用戶信息
//獲取當前用戶
String uid=UserProfileThread.getCurrentUser();
//獲取當前用戶對象
UserProfile user=UserProfileThread.getUserProfile();
示例2:spring框架中保證數據庫事務在同一個連接下執行
要想實現jdbc事務, 就必須是在同一個連接對象中操作,多個連接下事務就會不可控,需要借助分布式事務完成。那spring框架如何保證數據庫事務在同一個連接下執行的呢?
DataSourceTransactionManager 是spring的數據源事務管理器,它會在你調用getConnection()的時候從數據庫連接池中獲取一個connection, 然后將其與ThreadLocal綁定,事務完成后解除綁定。這樣就保證了事務在同一連接下完成。
ThreadLocal類提供set/get方法存儲和獲取value值,但實際上ThreadLocal類并不存儲value值,真正存儲是靠ThreadLocalMap這個類。
每個線程實例都對應一個TheadLocalMap實例,我們可以在同一個線程里實例化很多個ThreadLocal來存儲很多種類型的值,這些ThreadLocal實例分別作為key,對應各自的value,最終存儲在Entry table數組中。
我們看看ThreadLocal的set方法:
public class ThreadLocal<T> {
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// 省略其他方法
}
set的邏輯比較簡單,就是獲取當前線程的ThreadLocalMap,然后往map里添加KV,K是當前ThreadLocal實例,V是我們傳入的value。這里需要注意一下,map的獲取是需要從Thread類對象里面取,看一下Thread類的定義。
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
//省略其他
}
Thread類維護了一個ThreadLocalMap的變量引用。
因此,我們可以得出如下結論:
每個線程是一個Thread實例,其內部維護一個threadLocals的實例成員,其類型是ThreadLocal.ThreadLocalMap。
ThreadLocal本身并不是一個容器,我們存取的value實際上存儲在ThreadLocalMap中,ThreadLocal只是作為TheadLocalMap的key。
ThreadLocal實例有提供remove()方法,用于回收對象,清除對應的內存占用。這個方法通常容易被忽略,而導致出現了各種問題。如下面幾種:
線程復用:在“獲取接口的當前請求用戶”的例子中,Tomcat中是通過線程池來處理用戶請求的,而線程池中線程是復用的。肯定會出現一個線程前后被不同用戶的接口請求復用的情況,因此需要對用過的ThreaLocal變量進行覆蓋或清除。
內存溢出:由于ThreadLocalMap的生命周期跟Thread一樣長,如果創建的ThreadLocal變量很多,即對應的key占用的內存很大,但卻沒有手動刪除,到了一定程度就會導致內存泄漏。
“Java多線程ThreadLocal的知識點有哪些”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。