之前的接口是 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);
}
}