# 5.1 常用API以及使用

ThreadLocal的API文档如下图5-1所示,主要的方法有get、initialValue、remove、set和withInitial等5个方法。

图5-1 ThreadLocal的API

图5-1 ThreadLocal的API

# 5.1.1 常用API

  • initialValue()

ThreadLocal提供了两种实例化的方式: 继承ThreadLocal类,并重写initialValue()方法来定义初始化逻辑; 创建ThreadLocal的匿名子类,并在其构造器中初始化。 以下是两种方式的示例代码:

// 方式一:使用initialValue()方法初始化
public class MyThreadLocal extends ThreadLocal<String> {
    @Override
    protected String initialValue() {
        return "Initial Value";
    }
}

// 方式二:创建匿名子类并在构造器中初始化  
ThreadLocal<String> myThreadLocal = new ThreadLocal<String>() {
    @Override
    protected String initialValue() {
        return "Initial Value";
    }
};

// 或者直接在创建时初始化
ThreadLocal<String> myThreadLocal = ThreadLocal.withInitial(() -> "Initial Value");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

withInitial()方法是Java8引入的一个简化的构造方法,允许使用Lambda表达式来赋值。

  • get()

要从ThreadLocal中获取值,可以调用get方法:

MyThreadLocal myThreadLocal = new MyThreadLocal();
// 获取当前线程本地的值,初次调用会触发初始化
String value = myThreadLocal.get();
1
2
3
  • remove()

要从ThreadLocal中删除值,可以调用remove方法:

myThreadLocal.remove();
1
  • set()

设置当前线程的线程局部变量的值

myThreadLocal.set("New Value");
1

# 5.1.2 基本使用

下面的示例来说明ThreadLocal的基本使用。

public class ThreadLocalExample {

    // 创建一个ThreadLocal变量来存储线程的ID
    private static final ThreadLocal<Integer> threadId = new ThreadLocal<>() {
        @Override
        protected Integer initialValue() {
            // 初始值设置为当前线程的ID 
            return Thread.currentThread().getId(); 
        }
    };

    public static void main(String[] args) throws InterruptedException {
        // 创建并启动几个线程
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                // 获取并打印当前线程的 ID
                System.out.println("Thread ID: " + threadId.get());
            }).start();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

在这个示例中,每个线程都会打印其自己的线程ID,而不是其他线程的ID。

ThreadLocal的应用场景主要分为两类:

  • 避免对象在方法之间层层传递,打破层次间约束

例如请求调用链的唯一traceId,在很多地方都需要用到,层层往下传递,比较麻烦。 这时候就可以把traceId放到ThreadLocal中,在需要的地方可以直接获取。

  • 拷贝对象副本,减少初始化操作,并保证线程安全

比如数据库连接、Spring事务管理和SimpleDataFormat格式化日期等场景,都是使用的ThreadLocal, 即避免每个方法都初始化一个对象,又保证了多线程下的线程安全。

使用ThreadLocal保证SimpleDataFormat格式化日期的线程安全,代码如下。

public class ThreadLocalDemo {
    // 创建ThreadLocal
    static ThreadLocal<SimpleDateFormat> threadLocal =
            ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));


    public static void main(String[] args) {
        IntStream.range(0, 5).forEach(i -> {
            // 创建5个线程,分别从threadLocal取出SimpleDateFormat,然后格式化日期
            new Thread(() -> {
                try {
                    System.out.println(threadLocal.get().parse("2024-03-29 15:11:07"));
                } catch (ParseException e) {
                    throw new RuntimeException(e);
                }
            }).start();
        });
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19