非多线程,非高并发程序,出现ConcurrentModificationException异常
业务场景详情描述
- 业务需求:A模块有责任部门(单独字段)和入选部门(一个集合多条数据),我来做B模块:带出A模块的入选部门(一个集合多条数据),如果入选的部门集合中有责任部门,则把责任部门设置为Y,其他部门设置为N。如果入选的部门集合中没有责任部门,去查出A模块中责任部门后补充进入选部门集合中,同样把责任部门设置为Y,其他部门设置为N。
- 实现情况:主体为业务对象,并非部门,因此部门集合作为业务对象的一个属性成员变量展示(把集合操作完成后赋值给业务对象)。
- 我欠欠地使用了Lambda表达式去修改集合。。。
错误代码脱敏如下
//责任部门是否在入选部门集合中
if(null != deptList && deptList .size()>0) {//集合不为空,再遍历判断
deptList.forEach(dept->{
if(!dutyDept.isEmpty()) {//责任部门不为空dutyDept为责任部门的ID
if(dutyDept.equals(dept.getId())) { //是:此集合包含了责任部门,原集合赋给主对象mainObject
mainObject.setDeptList(deptList);
}else {//不是:此集合 不 包含 责任部门=加入一个对象(责任部门)到集合
Dept d = new Dept();
d.set...(...);//设置元素其它属性...
.....
d.setIsOwner("Y");
//保存对象
dService.save(d);
//添加到集合中
deptList.add(0, d);
mainObject.setDeptList(deptList);
}
......
}else {//责任部门为空
...
}
});
}else {//集合为空,直接添加
......
}
- .forEach()
- .add(0, d)
遍历时增加元素:造成ConcurrentModificationException异常
于是我学习了一下ConcurrentModificationException异常的前因后果
读的这篇文章
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Jiangshan11/article/details/83038857
总结如下
- ArrayList源码add如下
public void add(E e) {
//抛出ConcurrentModificationException异常的地方
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
/checkForComodification();/抛出异常的地方//
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
checkForComodification();所在方法
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
modCount就是修改次数,在具体的实现类中的Iterator中才会使用。在List集合中,ArrayList是List接口的实现类,modCount:表示list集合结构上被修改的次数。(在ArrayList所有涉及结构变化的方法中,都增加了modCount的值)list结构上别修改是指:改变了list的长度的大小或者是遍历结果中产生了不正确的结果的方式。add()和remove()方法会是modCount进行+1操作。modCount被修改后会产生ConcurrentModificationException异常, 这是jdk的快速失败原则。
问题解决方案
单线程情况:
(1)使用Iterator提供的remove方法,用于删除当前元素。
(2)建立一个集合,记录需要删除的元素,之后统一删除。
(3)不使用Iterator进行遍历,需要之一的是自己保证索引正常。
(4)使用并发集合类来避免ConcurrentModificationException,比如使用CopyOnArrayList,而不是ArrayList。
多线程情况:
(1)使用并发集合类,如使用ConcurrentHashMap或者CopyOnWriteArrayList。
此次错误解决方案
==用标记替代修改:遍历时做标记,处理标记修改集合,这样将两个动作分开做。==代码如下:
//查询出部门集合
List<Dept> deptList= xxxService.findDept(xxx);
//查询出此项目责任部门ID
String dutyDept = xxx.getId();
//责任部门是否在部门集合中:标记后统一处理
Boolean flagForEdpt = false;
if(null != deptList && deptList.size()>0) {//集合不为空,再遍历判断
for(int i=0;i<deptList.size();i++) {
if(dutyDept.equals(deptList.get(i).getId())) { //是:此集合包含了责任部门
flagForEdpt =true;
break; //直接跳出
}else {
flagForEdpt = false; //否:此集合不包含责任部门
}
}
}else {//集合为空-直接标记为需要修改
flagForEdpt = false;
}
if(flagForEdpt == false) {//缺少责任部门
Dept d = new Dept();
d.set...(...);//设置元素其它属性...
//保存对象
dService.save(d);
//添加到集合中
deptList.add(0, d);
}else if(flagForEdpt == true){//已包含责任部门
//处理一下......
}
mainObject.setDeptList(deptList);