本文的主题是ThreadLocal,作者将其用于接口自动化框架设计,并解决用例并发执行带来的线程不安全问题。
首先介绍ThreadLocal的原理与使用,其次介绍使用场景以及接口自动化的简单演示。
ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,可以称为线程本地变量。
查看ThreadLocal实现源码,会发现有四个方法比较重要。
public T get()
public void set(T value)
protected T initialValue()
public void remove()
下面通过实例给大家演示下ThreadLocal的原理与用法。
set就是设置值,get就是获取值,如果没有值,返回null,看上去,ThreadLocal就是一个单一对象的容器,比如:
public class ThreadLocalBasic {
static ThreadLocal local = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException{
// main 线程,设置local值为100
local.set(100);
Thread child = new Thread(){
// Thread-0 线程
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + ": "+ local.get());
local.set(200);
System.out.println(Thread.currentThread().getName() + ": "+local.get());
}
};
child.start();
child.join();
System.out.println(Thread.currentThread().getName()+ ": " + local.get());
}
}
通过结果发现,main线程对local变量的设置对Thread-0线程不起作用,Thread-0线程对local变量的改变也不会影响main线程,它们访问的虽然是同一个变量local,但每个线程都有自己的独立的值,这就是线程本地变量的含义。
initialValue用于提供初始值,可以通过匿名内部类的方式提供,当调用get方法时,如果之前没有设置过,会调用该方法获取初始值,默认实现是返回null。remove删掉当前线程对应的值,如果删掉后,再次调用get,会再调用initialValue获取初始值。
public class ThreadLocalInit {
static ThreadLocal local = new ThreadLocal(){
@Override
protected Integer initialValue(){
return 100;
}
};
public static void main(String[] args) {
System.out.println(local.get());
local.set(200);
local.remove();
System.out.println(local.get());
}
}
日期处理
ThreadLocal是实现线程安全的一种方案,比如对于DateFormat/SimpleDateFormat,每个线程使用自己的DateFormat,就不存在安全问题了,在线程的整个使用过程中,只需要创建一次,又避免了频繁创建的开销。
public class ThreadLocalDateFormat {
static ThreadLocal sdf = new ThreadLocal() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String date2String(Date date){
return sdf.get().format(date);
}
public static Date string2Date(String str) throws ParseException{
return sdf.get().parse(str);
}
}
ThreadLocal的典型用途是提供上下文信息,比如在一个Web服务器中,一个线程执行用户的请求,在执行过程中,很多代码都会访问一些共同的信息,比如请求信息、用户身份信息、数据库连接、当前事务等,它们是线程执行过程中的全局信息,如果作为参数在不同代码间传递,使用ThreadLocal就很方便,这样又在并发的场景下保证了线程安全。
下面以登录接口,测试查询接口为例,登录后直接将token存入ThreadLocal的token中,然后在queryTest使用token传递给queryBook接口用于鉴权。
public class ThreadLocalDemoTest {
static ThreadLocal token = new ThreadLocal();
// 模拟登陆,将token set到上下文
@BeforeTest
public static void login(){
token.set(LoginService.login("admin"));
}
@Test
public void queryTest(){
String response = QueryBookService.queryBook(token.get());
Assert.assertEquals(response, "管理员查询图书");
}
}
当然了,上述例子比较简单,线程本地变量仅存储token;如果需要存储更多的信息,那该咋办?
我们可以对ThreadLocal进行封装成ThreadLocalUtil,开发一个用于存储map的put和取map的getContextMap方法。
private final static ThreadLocal
用例实践:
public class ThreadLocalTest {
// 模拟登陆,将token set到上下文
@BeforeTest
public static void login(){
ThreadLocalUtil.put("token", LoginService.login("admin"));
ThreadLocalUtil.put("username", "软件质量保障");
}
@Test
public void queryTest(){
String response = QueryBookService.queryBook(ThreadLocalUtil.getContextMap().get("token").toString());
Assert.assertEquals(response, "管理员查询图书");
}
}
现在我邀请你进入我们的软件测试学习交流群:【
746506216
】,备注“入群”, 大家可以一起探讨交流软件测试,共同学习软件测试技术、面试等软件测试方方面面,还会有免费直播课,收获更多测试技巧,我们一起进阶Python自动化测试/测试开发,走向高薪之路。
喜欢软件测试的小伙伴们,如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一 键三连哦!