持续坚持原创输出,点击蓝字关注我吧
作者:软件质量保障
知乎:https://www.zhihu.com/people/iloverain1024
本文的主题是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<Integer> 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<Integer> local = new ThreadLocal<Integer>(){
@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<DateFormat> sdf = new ThreadLocal<DateFormat>() {
@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<String> token = new ThreadLocal<String>();
// 模拟登陆,将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<Map<String, Object>> THREAD_CONTEXT = new MapThreadLocal();
public static void put(String key, Object value) {
getContextMap().put(key, value);
}
static Map<String, Object> getContextMap() {
return THREAD_CONTEXT.get();
}
用例实践:
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, "管理员查询图书");
}
}
- END -
下方扫码关注 软件质量保障,与质量君一起学习成长、共同进步,做一个职场最贵Tester!
后台回复【测开】获取测试开发xmind脑图
后台回复【加群】获取加入测试社群!
往期推荐