2.8 ThreadLocal
在使用 Java 进行性能测试的过程中,将线程共享的变量通过用例设计优化转换成线程独享,是一种非常高效的解决线程安全问题的思路。java.lang.ThreadLocal 可以不必提前确定线程的数量,不必提前分配每个线程所需要的对象,直接全局定义一个 java.lang.ThreadLocal 对象,在多线程编程中使用 java.lang.ThreadLocal 提供的操作 API 即可完成线程独享对象的创建、修改和其他管理操作。
2.8.1 基础方法
ThreadLocal 实现原理基于每个线程都维护一个 ThreadLocalMap,这个映射表的键是 ThreadLocal 实例,值是对应的线程局部变量。这样,每个线程都可以拥有自己的独立变量副本,不受其他线程影响。
首先我们看一下如何创建一个 java.lang.ThreadLocal 对象:
ThreadLocal<String> threadLocal = new ThreadLocal<>();
创建方法需要声明泛型,下面是 ThreadLocal 的无参构造方法:
public ThreadLocal() {
}
该方法获取到的独享对象默认值是 null,如果你想设置其他默认值,可以使用以下语法:
ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "Hello FunTester !";
}
};
亦或者使用 java.lang.ThreadLocal#withInitial 方法创建 ThreadLocal 对象:
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "Hello FunTester !");
initialValue 方法内容如下:
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
使用该方法会创建一个 ThreadLocal 子类 java.lang.ThreadLocal.SuppliedThreadLocal,该类重写了 java.lang.ThreadLocal.SuppliedThreadLocal#initialValue 方法,内容如下:
@Override
protected T initialValue() {
return supplier.get();
}
其次我们对于变量两个最基本的操作:获取和修改。
- 获取:java.lang.ThreadLocal#get
- 修改:java.lang.ThreadLocal#set
API 非常简单,这里需要注意的就是返回值和参数值的类型需要与 ThreadLocal 创建是定义的泛型类型保持一致。
2.8.2 最佳实战
在性能测试中,ThreadLocal 通常用来将共享对象转换成独享解决线程安全的问题。下面用一个案例演示 ThreadLocal 最佳实战:
package org.funtester.performance.books.chapter02.section8;
/**
* ThreadLocal 演示
*/
public class ThreadLocalDemo {
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new ThreadLocal< String >() {// 创建一个线程本地变量
@Override
protected String initialValue() { // 重写初始化方法
return "Hello FunTester !";
}
};
for (int i = 0; i < 3; i++) {
new Thread(() -> { // 创建并启动3个线程
System.out.println("初始值: " + threadLocal.get()); // 获取本地变量值,并输出
threadLocal.set(Thread.currentThread().getName() + " Hello FunTester !"); // 设置本地变量值
System.out.println("修改后值: " + threadLocal.get()); // 获取本地变量值,并输出
}).start();
}
}
}
控制台输出:
初始值: Hello FunTester !
修改后值: Thread-0 Hello FunTester !
初始值: Hello FunTester !
修改后值: Thread-1 Hello FunTester !
初始值: Hello FunTester !
修改后值: Thread-2 Hello FunTester !
可以看到,每个线程打印的初始值都是一样的,重新赋值之后,每个线程打印的值都变得不一样了。这就说明每个线程实际获取的对象并不是同一个对象,也就实现了将共享对象转换成独享对象的设计思路,解决了线程安全的问题。
2.8.3 使用场景
除了以上使用场景外,java.lang.ThreadLocal 在性能测试使用的场景并不多。但在 ThreadLocal 使用过程中,需要注意潜在的内存泄漏问题。
如果 ThreadLocal 实例在某个类中定义为 static,而该类又被类加载器加载,那么这个 ThreadLocal 对象将一直存在于内存中,直到线程结束或者手动调用 remove() 方法将其移除。
所以在性能测试中如果使用 ThreadLocal 来解决线程安全问题,需要对线程管理更加严格。对于新手而言,使用最佳实战中的方式是安全可靠的,若还是无法满足需求,则应该抛弃 ThreadLocal,寻求其他简单、可靠的解决方案。
书的名字:从 Java 开始做性能测试 。
如果本书内容对你有所帮助,希望各位不吝赞赏,让我可以贴补家用。赞赏两位数可以提前阅读未公开章节。我也会尝试制作本书的视频教程,包括必要的答疑。
FunTester 原创精华