目录

hymn

忽有故人心头过,回首山河已是秋。

使用ThreadLocal

在一个线程中,横跨若干方法调用,需要传递的对象,我们通常称之为上下文(Context),它是一种状态,可以是用户身份、任务信息等。

给每个方法增加一个context参数非常麻烦,而且有些时候,如果调用链有无法修改源码的第三方库,User对象就传不进去了。

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

ThreadLocal实例通常总是以静态字段初始化如下:

static ThreadLocal<User> threadLocalUser = new ThreadLocal<>();

它的典型使用方式如下:

void processUser(user) {
    try {
        threadLocalUser.set(user);
        step1();
        step2();
    } finally {
        threadLocalUser.remove();
    }
}

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

void step1() {
    User u = threadLocalUser.get();
    log();
    printUser();
}

void log() {
    User u = threadLocalUser.get();
    println(u.name);
}

void step2() {
    User u = threadLocalUser.get();
    checkUser(u.id);
}

注意到普通的方法调用一定是同一个线程执行的,所以,step1()step2()以及 log()方法内,threadLocalUser.get()获取的 User对象是同一个实例。

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

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

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

最后,特别注意 ThreadLocal一定要在 finally中清除:

try {
    threadLocalUser.set(user);
    ...
} finally {
    threadLocalUser.remove();
}

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

小结

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

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

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


标题:使用ThreadLocal
作者:hymn
地址:https://dxyhymn.com/articles/2020/12/04/1607076115253.html