问题描述
CompletableFuture中使用ArrayList.add()添加元素,造成集合中缺失元素。
代码片段
@Test
public void testCompletableFuture() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(8,
16, 1, TimeUnit.MINUTES,
new ArrayBlockingQueue<>(20),
r -> new Thread(r, "Qa-ThreadPool")
, new ThreadPoolExecutor.CallerRunsPolicy());
List<RobotQaCountVO> robotQaCountVOs = new ArrayList<>();
List<String> robotIds = CollUtil.newArrayList("aaa", "bbb", "ccc");
if (CollectionUtils.isNotEmpty(robotIds)) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
robotIds.forEach(robotId -> {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println(robotId + "当前线程:" + Thread.currentThread().getName());
long count = 123;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
robotQaCountVOs.add(new RobotQaCountVO(robotId, count));
}, executor);
futures.add(future);
});
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).join();
}
System.out.println("robotQaCountVOs:" + robotQaCountVOs);
}
3个CompletableFuture并发运行任务,并将结果封装实体add添加进ArrayList,预期的结果应该是集合中有3个元素,但实际结果有时有2个,有时有3个元素,即可能产生元素缺失的情况。
robotQaCountVOs:[null, null, RobotQaCountVO(robotId=bbb, qaCount=123)]
原因
ArrayList是线程不安全的集合,并发操作时,就会产生上述现象。虽然我们都知道ArrayList是线程不安全的,但实际使用中还是难免会出错,特此记录一下。
ArrayList.add()源码
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
显然,size++不是原子操作,添加元素时,可能会覆盖元素。
解决方案
- Vector
- CopyOnWriteArrayList
- Collections.synchronizedList(new ArrayList<>())
Vector和ArrayList基本一样,但操作元素方法都加了synchronized
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
Collections.synchronizedList(new ArrayList<>())返回一个SynchronizedList,也是操作元素方法加了synchronized
public boolean add(E e) {
synchronized (mutex) {return c.add(e);}
}
CopyOnWriteArrayList是写的时候先复制一个新的数组,写完了再把新数组替换旧数组,适合读多写少的场景,并且允许读写不一致。此处场景就是写元素,所以不采用CopyOnWriteArrayList。
最终使用了Collections.synchronizedList(new ArrayList<>())
修改后代码
@Test
public void testCompletableFuture() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(8,
16, 1, TimeUnit.MINUTES,
new ArrayBlockingQueue<>(20),
r -> new Thread(r, "Qa-ThreadPool")
, new ThreadPoolExecutor.CallerRunsPolicy());
List<RobotQaCountVO> robotQaCountVOs = Collections.synchronizedList(new ArrayList<>());
List<String> robotIds = CollUtil.newArrayList("aaa", "bbb", "ccc");
if (CollectionUtils.isNotEmpty(robotIds)) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
robotIds.forEach(robotId -> {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println(robotId + "当前线程:" + Thread.currentThread().getName());
long count = 123;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
robotQaCountVOs.add(new RobotQaCountVO(robotId, count));
}, executor);
futures.add(future);
});
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).join();
}
System.out.println("robotQaCountVOs:" + robotQaCountVOs);
}
结果多次运行也正常了
robotQaCountVOs:[RobotQaCountVO(robotId=aaa, qaCount=123), RobotQaCountVO(robotId=bbb, qaCount=123), RobotQaCountVO(robotId=ccc, qaCount=123)]