导读:本文从业务为背景,结合实例给出最佳使用实践,来分析ArrayList线程安全问题,以便帮助各开发人员构建出稳定、高效的Java应用服务。
背景
由于目前当前产品用的SpringBoot 版本为2.1.17.RELEASE。
在JobExecutionExitCodeGenerator 监听事件中读取事件ordinal 时候发现多线程压测中偶发出现数据错乱问题。当时还以为是元数据扫描脏数据导致,没有留意;但是切换新环境后依然出现类似问题。
public class JobExecutionExitCodeGenerator
implements ApplicationListener<JobExecutionEvent>, ExitCodeGenerator {
private final List<JobExecution> executions = new ArrayList();
public JobExecutionExitCodeGenerator() {
}
public void onApplicationEvent(JobExecutionEvent event) {
this.executions.add(event.getJobExecution());
}
public int getExitCode() {
Iterator var1 = this.executions.iterator();
JobExecution execution;
do {
if (!var1.hasNext()) {
return 0;
}
execution = (JobExecution)var1.next();
} while(execution.getStatus().ordinal() <= 0);
return execution.getStatus().ordinal();
}
}
就引起了我们的关注,最后定位到监听器可以被多个线程并发调用,线程是不安全的。
private final List<JobExecution> executions = new ArrayList();
因此这里应该调整为使用CopyOnWriteArrayList。
public class JobExecutionExitCodeGenerator implements
ApplicationListener<JobExecutionEvent>, ExitCodeGenerator {
// CopyOnWriteArrayList 保证线程安全
private final List<JobExecution> executions = new CopyOnWriteArrayList();
public JobExecutionExitCodeGenerator() {
}
public void onApplicationEvent(JobExecutionEvent event) {
this.executions.add(event.getJobExecution());
}
public int getExitCode() {
Iterator var1 = this.executions.iterator();
JobExecution execution;
do {
if (!var1.hasNext()) {
return 0;
}
execution = (JobExecution)var1.next();
} while(execution.getStatus().ordinal() <= 0);
return execution.getStatus().ordinal();
}
}
CopyOnWriteArrayList类图结构
原理
CopyOnWriteArrayList采用了一种读写分离的并发策略。CopyOnWriteArrayList容器允许并发读,读操作是无锁的,性能较高。至于写操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。示意图如下:
总结
CopyOnWriteArrayList这是一个ArrayList的线程安全的变体,其原理大概可以通俗的理解为:初始化的时候只有一个容器,很长一段时间,这个容器数据、数量等没有发生变化的时候,大家(多个线程),都是读取(假设这段时间里只发生读取的操作)同一个容器中的数据,所以这样大家读到的数据都是唯一、一致、安全的,但是后来有人往里面增加了一个数据,这个时候CopyOnWriteArrayList 底层实现添加的原理是先copy出一个容器(可以简称副本),再往新的容器里添加这个新的数据,最后把新的容器的引用地址赋值给了之前那个旧的的容器地址,但是在添加这个数据的期间,其他线程如果要去读取数据,仍然是读取到旧的容器里的数据。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有