人人都能学会系列之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);
    }
}
相关文章
相关标签/搜索