java 多线程操作同一集合问题 ConcurrentModificationException

本文介绍了在Kafka消费者监听中使用线程池处理业务逻辑时,如何避免因ArrayList并发修改引发的异常。通过引入CopyOnWriteArrayList,解决了并发安全性问题。虽然CopyOnWriteArrayList在读多写少的场景下适用,但存在延迟和性能损耗。代码示例展示了配置Spring的ThreadPoolTaskExecutor以及在业务处理中使用CopyOnWriteArrayList来确保线程安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实际业务场景:

业务描述:
kafka监听开启线程执行后续业务逻辑
问题:
ArrayList 在迭代期间如果修改集合的内容,会抛出 ConcurrentModificationException 异常。
解决
CopyOnWriteArrayList 替换 ArrayList可以解决该问题,想知道原理的,建议上网查下CopyOnWriteArrayList 就行。
下面贴一部分我的代码

1.线程工具类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
public class ExecutorConfig {
    @Bean("taskExecutor")
    public Executor taskExecutro(){
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(50);
        taskExecutor.setMaxPoolSize(100);
        taskExecutor.setQueueCapacity(10000);
        taskExecutor.setKeepAliveSeconds(60);
        taskExecutor.setThreadNamePrefix("线程名前缀--");
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        taskExecutor.setAwaitTerminationSeconds(60);
        return taskExecutor;
    }
}

2.业务处理(部分)
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

import java.util.concurrent.CopyOnWriteArrayList;

@Component
@Slf4j
public class xxxxConsumer {
	//修改前
	//private static final HashMap<String, List<Double>> hashListxxx = new HashMap<>();
	private static final HashMap<String, CopyOnWriteArrayList<Double>> hashListxxx = new HashMap<>();
 	
 	//监听程序
 	@KafkaListener(topics = "${spring.kafka.topic.data_tcp}", groupId = "group_data_tcp")
    public void consumer(ConsumerRecord<String, String> record) {
        taskExecutor.execute(() -> {
            mydeal(record);
        });
    }

	//kafka消费业务类
	private void mydeal(ConsumerRecord<String, String> record){
		//1.写场景
		CopyOnWriteArrayList<Double> dddList = new CopyOnWriteArrayList<>();
		dddList.add(我的业务值);
	    hashListxxx .put("key", dddList);
		>>>....
		//2.读场景,在这里我是计算方差时用流的方式读取
		//计算方差
		double avg = 这组数的平均值;
		double sumPow = hashListxxx .get(key).stream().mapToDouble(v -> Math.pow((v - avg), 2)).sum();
    	double sPow = sumPow / hashListSigma.get(did).size();
	
	}


}
  • 正是因为多线程下可能迭代期间修改内容,所以产生上述ConcurrentModificationException异常
  • 该异常出现原理在此不深究,大概为循环读操作是导致的迭代器某一数量差异,刚好此时另一线程执行写入操作,导致
3.CopyOnWriteArrayList源码
  • 它继承了List的接口,所以业务中的操作方法不需要改变,使用很方便
  • 要注意的是:它适用于读多少写的业务场景,并且修改有一定延迟,不能保证实时为最新的数据。牺牲性能换来的是多线程下的安全

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ywh22122

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值