本文咱們來看下java.lang
包中的ThreadLocal,它賦予咱們給每一個線程存儲本身數據的能力。java
ThreadLocal容許咱們存儲的數據只能被特定的線程``訪問
。
咱們如今存儲一個整形並把它和一個特定的線程綁定:數據庫
ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
接下來當咱們在某個線程中想使用這個值的時候,咱們只須要調用get()或set()方法,簡單的說,咱們能夠把ThreadLocal理解成數據都存在一個map中,使用線程對象做爲key。api
當咱們在當前線程中調用threadLocalValue的get()方法時,咱們能拿到整形值1:ide
threadLocalValue.set(1); Integer result = threadLocalValue.get();
咱們可使用ThreadLocal的withInitial()方法並傳入一個supplier來建立一個實例:測試
ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);
想要刪除這個值的時候咱們只須要調用一下remove()方法就行了ui
threadLocal.remove();
在敘述怎麼合適的使用ThreadLocal以前咱們先來看一個不用ThreadLocal的例子,而後咱們再修改例子比較一下。this
有這麼一個程序須要給每個用戶ID存儲對應的用戶上下文信息:線程
public class Context { private String userName; public Context(String userName) { this.userName = userName; } }
咱們給每一個用戶新起一個線程,建立了一個實現了Runnable接口的SharedMapWithUserContext類,run()方法中的UserRepository會查詢數據庫返回傳入用戶ID的用戶上下文信息。code
接下來咱們把用戶信息以用戶ID爲key存入ConcurentHashMap中:對象
public class SharedMapWithUserContext implements Runnable { public static Map<Integer, Context> userContextPerUserId = new ConcurrentHashMap<>(); private Integer userId; private UserRepository userRepository = new UserRepository(); @Override public void run() { String userName = userRepository.getUserNameForUserId(userId); userContextPerUserId.put(userId, new Context(userName)); } // standard constructor }
咱們來測試一下代碼,給兩個用戶ID建立兩個線程,在運行結束設置斷言:userContextPerUserId的大小爲2:
SharedMapWithUserContext firstUser = new SharedMapWithUserContext(1); SharedMapWithUserContext secondUser = new SharedMapWithUserContext(2); new Thread(firstUser).start(); new Thread(secondUser).start(); assertEquals(SharedMapWithUserContext.userContextPerUserId.size(), 2);
咱們重寫一下咱們的例子,此次把用戶信息存在ThreadLocal中,每一個線程都有本身的ThreadLocal實例。
咱們在使用的時候要特別當心由於每一個ThreadLocal實例都關聯了一個特定的線程,在咱們的例子中,咱們給每一個用戶ID建立了一個專用的線程,而且這是咱們本身建立出來的,咱們能夠徹底控制它們。(爲何這麼說後面會解釋到)
run()方法拿到用戶信息構造上下文對象並使用ThreadLocal的set()方法存儲起來:
public class ThreadLocalWithUserContext implements Runnable { private static ThreadLocal<Context> userContext = new ThreadLocal<>(); private Integer userId; private UserRepository userRepository = new UserRepository(); @Override public void run() { String userName = userRepository.getUserNameForUserId(userId); userContext.set(new Context(userName)); System.out.println("thread context for given userId: " + userId + " is: " + userContext.get()); } // standard constructor }
咱們開啓兩個線程測試一下:
ThreadLocalWithUserContext firstUser = new ThreadLocalWithUserContext(1); ThreadLocalWithUserContext secondUser = new ThreadLocalWithUserContext(2); new Thread(firstUser).start(); new Thread(secondUser).start();
代碼運行完能看到ThreadLocal在每一個線程中都設置了值:
thread context for given userId: 1 is: Context{userNameSecret='18a78f8e-24d2-4abf-91d6-79eaa198123f'} thread context for given userId: 2 is: Context{userNameSecret='e19f6a0a-253e-423e-8b2b-bca1f471ae5c'}
當前例子
若是咱們使用ExecutorService並往裏面提交Runnable任務,使用ThreadLocal會出現不肯定的結果。由於咱們不能肯定操做每一個用戶ID的Runnable任務每次都是被相同的線程操做,所以ThreadLocal會被不用的用戶ID公用。
不一樣的業務使用場景不一樣,也不能一棒子打死,好比下面這個例子
最近在使用Spring的狀態機的時候,處理一個動做發起的邏輯須要調用狀態機實例的sendEvent方法發送消息,狀態機返回當前事件是否處理成功,但不方便使用的是這個sendEvent返回的值是個boolean類型,裏面若是有錯誤,我拿不到錯誤信息,拋異常也會被內部捕獲到返回false:
boolean sendEvent(Message<E> event);
這個例子和上述不一樣的地方在於我使用狀態機在每一個Runnable任務執行中只須要拿到本次運行的信息,而不須要把當前信息和後面提交的任務共享使用。因此我調用get()拿到我在另外一處邏輯裏設置的錯誤信息時,我當即調用remove()表示本次ThreadLocal結束了。
狀態機判斷邏輯:
if (checkInvoice == null) { @SuppressWarnings("unchecked") ThreadLocal<String> requestError = (ThreadLocal<String>) messageHeaders.get(THREAD_LOCAL_NAME); Objects.requireNonNull(requestError).set("發票不能爲空"); return false; }
狀態機外部處理邏輯:
// 返回的結果是Guard的返回結果 boolean isHandleSuccess = stateMachine.sendEvent(message); if (isHandleSuccess) { S currentState = stateMachine.getState().getId(); if (previousState != currentState) { stateMachinePersister.persist(stateMachine, uniqueKey); } else { log.info("狀態沒有變動,不須要持久化狀態。previousState={},currentState={}", previousState, currentState); } if (previousState.checkNextState(currentState)) { return buildSuccessResult(object, event.desc() + SUCCESS_TEXT); } else { String error = requestError.get(); log.info("ThreadLocal value:{}", error); requestError.remove(); return buildFailureResult(object, error); } }