FunTester 【连载 13】ThreadLocal 类

FunTester · 2025年01月21日 · 366 次阅读

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 原创精华

【连载】从 Java 开始性能测试

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册