接口测试框架多线程并发线程不安全处理
之前的接口是 Api,ApiModel,ApiHelper的形式。
用ApiHelper专门处理api的数据加工,发送请求并获取返回。
后来发现这种写法在单线程没问题,多线程并发下时,apiHelper处理数据会出现数据错乱。
大致原因是apiHelper作为一个工具类,对api的所有操作都是static,就算sychronize之后,一些公用的值还是线程不安全,导致数据中途处理会串信息。
我这边的解决办法直接把ApiHelper和Api合并了,合并后代码更简洁,一些数据的处理写起来更像流式数据的写法,而且线程控制更加安全。
另:线上出现了一个SimpleDateFormat线程不安全的缺陷,之前一直没写demo,因为没用框架跑并发,这次并发加上了,顺便写了个demo,记录一下。
package testdemo.junit5demo; import io.qameta.allure.Feature; import io.qameta.allure.Owner; import io.qameta.allure.Story; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Date; import static org.junit.jupiter.api.Assertions.assertEquals; //CorpStaffDataAnalyseServiceImpl //还有一个解决方案,手动加锁 @Slf4j @Feature("SimpleDateFormat并发不安全示例") @Owner("zhzh.yin") public class ConcurrentTest { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); //StringBuffer是线程安全的,也就是多线程修改同一个StringBuffer对象的时候,过程是同步的,当然这就导致了StringBuffer的效率降低,毕竟如果要提升安全性,就必须要损失一定的效率。 //synchronized //加锁 private static final ThreadLocal<SimpleDateFormat> THREAD_LOCAL = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; // @RepeatedTest(500) @Story("并发报错:simpleDateFormat线程不安全") @Execution(ExecutionMode.CONCURRENT) //解决方案:用DateTimeFormatter public void testFailure() throws ParseException, InterruptedException { String dateString = simpleDateFormat.format(new Date()); log.info(dateString); Date time = simpleDateFormat.parse(dateString); String dateString2 = simpleDateFormat.format(time); assertEquals(dateString, dateString2); } // @RepeatedTest(500) @Story("解决方案:局部变量") @Execution(ExecutionMode.CONCURRENT) //解决方案:局部变量 public void testSuc2() throws ParseException, InterruptedException { SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateString = simpleDateFormat1.format(new Date()); log.info(dateString); Date time = simpleDateFormat1.parse(dateString); String dateString2 = simpleDateFormat1.format(time); assertEquals(dateString, dateString2); } // @RepeatedTest(500) @Story("解决方案:ThreadLocal") @Execution(ExecutionMode.CONCURRENT) //解决方案:使用ThreadLocal,每个线程都拥有自己的SimpleDateFormat对象副本。 public void testSuc3() throws ParseException, InterruptedException { SimpleDateFormat simpleDateFormat2 = THREAD_LOCAL.get(); if (simpleDateFormat2 == null) { simpleDateFormat2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); }String dateString = simpleDateFormat2.format(new Date()); log.info(dateString); Date time = simpleDateFormat2.parse(dateString); String dateString2 = simpleDateFormat2.format(time); assertEquals(dateString, dateString2); } // @RepeatedTest(500) @Story("解决方案:使用DateTimeFormatter") @Execution(ExecutionMode.CONCURRENT) public void testSuc1() throws ParseException, InterruptedException { String dateNow = LocalDateTime.now().format(dtf); String dateNow2=LocalDateTime.parse(dateNow,dtf).format(dtf); assertEquals(dateNow, dateNow2); } }