除了使用synchronized
同步符号外,Java
中的ThreadLocal
是另一种实现线程安全的方法。在进行性能测试用例的编写过程中,比较简单的办法就是直接使用synchronized
关键字,修饰对象、方法以及类。但是使用synchronized
同步,这可能会影响应用程序的可伸缩性以及运行效率。但是如果要在多个线程之间共享对象又要保障线程安全,则除了synchronized
之外没有特别适合测试的方法。
Java
中的ThreadLocal
是实现线程安全的另一种方法,它不满足同步要求,而是通过为每个线程提供Object
的显式副本来消除共享。由于不再共享对象,因此不需要同步,它可以提高应用程序的可伸缩性和运行效率。
在本文中,会介绍有关ThreadLocal
的基础知识点点,Demo
中ThreadLocal
的简单示例。
很多人几乎都没有用过ThreadLocal
类,因为在测试中能用到的地方实在太少了,而且测试脚本的性能一般来讲都会很高,远超被测服务的处理能力,所以即使全部使用synchronized
也不会有任何问题。
但是ThreadLocal
有很多真正的使用场景,这就是为什么将其添加到标准Java
平台库中的原因。尽管知道现在多线程编程测试中对于ThreadLocal
应用并不多,但是我会在后期多进行一些实践,分享给各位。
以下是Java
中ThreadLocal
类的一些众所周知的用法:
ThreadLocal
非常适合实现每个线程单例类或每个线程上下文信息(例如事务ID
)。ThreadLocal
中,并且将其使用变为线程安全的。ThreadLocal
的经典示例之一是共享SimpleDateFormat
。由于SimpleDateFormat
不是线程安全的,因此使用全局格式化程序可能无法正常工作,但是使用每个线程格式化程序当然可以工作。ThreadLocal
提供了另一种扩展Thread
的方法。如果要保留信息或将信息从一个方法调用传递到另一个方法,则可以使用ThreadLocal
进行传递。没有两个线程可以看到彼此的ThreadLocal
变量。J2EE
应用程序服务器中有一个ThreadLocal
的真实示例,该服务器使用Java ThreadLocal
变量来跟踪事务和安全上下文。
为了避免过多的创建和共享全局实例时的切换成本,将诸如数据库连接之类的重对象作为 ThreadLocal 共享是很有意义的。
package com.fun.ztest.java;
import com.fun.frame.SourceCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
* ThreadLocal演示测试类
*/
public class FunTester extends SourceCode {
public static Logger logger = LoggerFactory.getLogger(FunTester.class);
/**
* 这个是重点,通过ThreadLocal类重建线程私有的对象
*/
private static final ThreadLocal<Object> format = new ThreadLocal() {
@Override
protected Object initialValue() {
Object funTester = new Object();
logger.info("初始化对象,线程: {} 对象: {}", Thread.currentThread().getName(), funTester.hashCode());
return funTester;
}
};
public static void main(String args[]) throws IOException, InterruptedException {
for (int i = 0; i < 5; i++) {
Thread t = new Thread(new Fun());
t.start();
}
}
/**
* 获取对象
*
* @return
*/
public static Object get() {
return format.get();
}
static class Fun implements Runnable {
@Override
public void run() {
logger.info("线程: {} 对象: {}", Thread.currentThread().getName(), FunTester.get().hashCode());
}
}
}
INFO-> 当前用户:fv,IP:10.60.193.37,工作目录:/Users/fv/Documents/workspace/fun/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
INFO-> 初始化对象,线程: Thread-1 对象: 347384150
INFO-> 初始化对象,线程: Thread-2 对象: 142607688
INFO-> 线程: Thread-1 对象: 347384150
INFO-> 线程: Thread-2 对象: 142607688
INFO-> 初始化对象,线程: Thread-3 对象: 1008357237
INFO-> 初始化对象,线程: Thread-4 对象: 559951532
INFO-> 线程: Thread-3 对象: 1008357237
INFO-> 线程: Thread-4 对象: 559951532
INFO-> 初始化对象,线程: Thread-5 对象: 748958847
INFO-> 线程: Thread-5 对象: 748958847
Process finished with exit code 0
如果查看上述程序的输出,则会发现,当不同的线程调用ThreadLocal
类的get()
方法而不是调用其initialValue()
方法时,该方法将为该线程创建Object
的互斥实例对象。 由于Object
在线程之间不共享,并且实质上在创建它自己的线程安全对象或者方法的线程本地是完全线程安全的。
ThreadLocal
在JDK 1.2
上引入,但后来在JDK 1.4
中进行了泛化,以在ThreadLocal
变量上引入类型安全性。ThreadLocal
通常与Thread
一起使用,由Thread
执行的所有代码都可以访问ThreadLocal
变量,但是两个线程看不到彼此的ThreadLocal
变量。ThreadLocal
变量的互斥副本,该副本在线程完成或死亡(正常情况下或由于任何异常)后才有进行垃圾回收,因为这些ThreadLocal
变量没有任何其他线程引用。ThreadLocal
变量通常是类中的私有静态字段,并在Thread
中维护其状态。不要误解ThreadLocal
是Synchronization
的替代方法,它全部取决于你自己的程序设计。如果设计允许每个线程拥有自己的对象副本,则可以使用ThreadLocal
。
这里一个处理requestid
的类,通过ThreadLocal
使用,可以保证每个请求都拥有唯一的一个追踪标记。
public class TraceKeyHolder {
private static ThreadLocal<String> threadLocal = new ThreadLocal();
public TraceKeyHolder() {
}
public static String getTraceKey() {
return (String)threadLocal.get();
}
public static void setTraceKey(String traceKey) {
threadLocal.set(traceKey);
}
public static void clear() {
threadLocal.remove();
}
}