使用ThreadLocal

对象传递

在一个线程中,如果我们想要传递一个对象,一般是怎么做的呢?我们看下面这个方法,通过传参的方式可以在方法之间传递对象:

public void process(Order order) {
    checkParams(order);
    createOrder(order);
    notifyMessages(order);
}

这样做一般来说是没有任何问题的,但是方法中的方法有时候又会调用其他很多方法,比如:

public void createOrder(Order order) {
    checkIfExists(order);
    doCreate(order);
}

这样会导致Order对象传递到所有地方。

ThreadLocal

这种在一个线程中,在若干方法中调用,需要传递的对象,我们通常称之为上下文(Context),它是一种状态。给每个方法增加一个 context 参数非常麻烦,并且有时候,如果调用链有无法修改源码的第三方库,我们的对象就传不进去了。

Java 标准库提供了一个特殊的ThreadLocal,它可以在一个线程中传递同一个对象。

我们通常使用如下方式来初始化一个ThreadLocal对象:

public static ThreadLocal threadLocal = new ThreadLocal<>();

他的使用方法也非常简单:

void processUser(Order order) {
    try {
        threadLocal.set(order);
        doSomething();
        doOthers();
    } finally {
        threadLocal.remove();
    }
}

通过设置一个Order实例关联到ThreadLocal中,在移除之前,所有方法都可以随时获取到该Order实例。

void doSomething() {
    Order order = threadLocalUser.get();
   System.out.println(order);
}

void doOthers() {
    Order order = threadLocalUser.get();
   System.out.println(order);
}

实际上,可以把ThreadLocal看成一个全局Map:每个线程获取ThreadLocal变量时,总是使用Thread自身作为key:

Object threadLocalValue = threadLocalMap.get(Thread.currentThread());

因此,ThreadLocal相当于给每个线程都开辟了一个独立的存储空间,各个线程的ThreadLocal关联的实例互不干扰。

最后,特别注意ThreadLocal一定要在finally中清除。这是因为当前线程执行完相关代码后,很可能会被重新放入线程池中,如果ThreadLocal没有被清除,该线程执行其他代码时,会把上一次的状态带进去。

为了保证能释放ThreadLocal关联的实例,我们可以通过AutoCloseable接口配合try (resource) {...}结构,让编译器自动为我们关闭。例如,一个保存了当前订单id的ThreadLocal可以封装为一个OrderContext对象:

public class OrderContext implements AutoCloseable {

    static final ThreadLocal ctx = new ThreadLocal<>();

    public OrderContext(String orderId) {
        ctx.set(orderId);
    }

    public static String currentOrderId() {
        return ctx.get();
    }

    @Override
    public void close() {
        ctx.remove();
    }
}

使用的时候,我们借助try (resource) {...}结构,可以这么写:

try (var ctx = new OrderContext("Bob")) {
    String currentOrderId = OrderContext.currentOrderId();

这样就在OrderContext中完全封装了ThreadLocal,外部代码在try (resource) {...}内部可以随时调用OrderContext.currentOrderId()获取当前线程绑定的订单id。

写在最后

  • • ThreadLocal表示线程的“局部变量”,它确保每个线程的ThreadLocal变量都是各自独立的

  • • ThreadLocal适合在一个线程的处理流程中保持上下文(避免了同一参数在所有方法中传递)

  • • 使用ThreadLocal要用try ... finally结构,并在finally中清除


请使用浏览器的分享功能分享到微信等