今天專案中遇到一個問題:利用SecurityContextHolder
獲取使用者登入資訊的時候一直報空指標異常。網上查詢原來這是一個執行緒級別的全域性變數,只能在主執行緒上訪問。
在Spring中,對例如如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等非執行緒安全狀態採用ThreadLocal進行處理,讓它們也成為執行緒安全的狀態。
概述
Threadlocal
是Thread的區域性變數,它為每個使用該變數的執行緒提供獨立的變數副本。每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本,最終目的是實現執行緒之間的資料隔離(有個關鍵字叫Synchronized
是實現執行緒之間的資料隔離)。
ThreadLocal
提供的四個方法作用如下:
- initialValue() //返回該執行緒區域性變數的初始值 ,在get操作沒有對應的值時,呼叫此方法
- get() //返回當前執行緒所對應的執行緒變數副本
- set(Object value)//設定當前執行緒的執行緒變數副本
- remove() //將當前執行緒變數副本的值刪除
原理
介紹原理之前需要了解另外兩個類:
- Thread //用於操作執行緒 ,內部有
ThreadLocal
和ThreadLocalMap
屬性- ThreadLocalMap //
ThreadLocal
內部類,用來儲存資料,儲存了以threadLocal為key,需要隔離的資料為value。
Thread內有個threadLocals,該屬性用來儲存該執行緒本地變數。ThreadLocal進行set()和get()操作時都要首先獲取當前執行緒,然後獲取執行緒內的threadLocals,如果threadLocals存在,則以threadlocal為key調取vlaue或set值。這樣每個執行緒都有自己的資料,就做到了不同執行緒間資料的隔離,保證了資料安全。
public
class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
複製程式碼
案例
public class ThreadLocalTest {
private static String s1;
private static ThreadLocal<String> s2 = new ThreadLocal<>();
public static void main(String[] args) {
s1 = "test1";
threadLabel.set("test1");
//開啟一個新執行緒,改變s1,s2的值
Thread thread = new Thread() {
@Override
public void run() {
s1= "test2";
s2.set("test2");
}
};
thread.start();
System.out.println("s1值為" +s1 );//獲取到s1變化為為test2
System.out.println("s2值為" +s2.get());//獲取到的s2仍然為為test1,在另外執行緒賦值對main執行緒無影響
}
}
複製程式碼