人人都能學會系列之ThreadLocal

一、概覽

本文咱們來看下java.lang包中的ThreadLocal,它賦予咱們給每一個線程存儲本身數據的能力。java

二、ThreadLocal API

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

三、存儲用戶數據在ConcurentHashMap中

有這麼一個程序須要給每個用戶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實例。

咱們在使用的時候要特別當心由於每一個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'}

五、當心把ThreadLocal和ExecutorService一塊兒使用

  • 當前例子
    若是咱們使用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);
    }
}
相關文章
相關標籤/搜索