JDK迁移之坑-1
从IBM JDK 6 迁移到 Open JDK 8, 大部分是没有问题的。少部分我们也踩了些坑。这是第一个坑。
if(!isCaseFlag){
if(isConfig){
//do business logic
return;
}
if(isCaseFlagl){
//do business logic
}else if(isCaseFlag2){
List<String> statusTypes = getStatusTypes();
for(String statusType : statusTypes){
if (isMatchDel.etedCondition(statusType)){
statusTypes.remove(statusType)
}
}
}
}
运行这段代码,发现IBM JDK 6 没有报错,但是Open JDK 就会抛出 java.util.ConcurrentModificationException。
‘java.util.ConcurrentModificationException‘是Java开发中比较常见的错误,当时看到这个错误的时候,我们猜测可能是有两个线程同时对ArrayList进行操作,一个线程对ArrayList进行遍历,一个线程对ArrayList进行添加或者删除,导致这个异常信息被抛出。
但是查看业务代码逻辑后,我们发现这个错误并不是在两个线程操作ArrayList是发生的。而是在同一个线程中,在遍历ArrayList的过程中删除了元素,导致了ConcurrentModificationException被抛出。
在看到业务代码后,我们基本上可以确定这是由于用户业务逻辑代码不规范导致的。如果想在遍历ArrayList的同时删除元素,应该用迭代器来遍历ArrayList,而不是采用for-each语句。那么如果将遍历方式修改后,Open JDK应该就不会抛出ConcurrentModificationException。
但是随之而来的疑惑是,为什么IBM JDK 6 没有报错?
在不断地深入查看IBM JDK和Open JDK源码后,最终找到出现问题的根本原因。ArrayList在两个不同版本JDK中的实现上是有差异的,在特定情况下,IBM JDK ArrayList支持在遍历的请况下删除元素。
IBM JDK中 ArrayList中循环遍历的类似代码实现
int pos = -1;
public boolean hasNex() {
return pos + 1 < size();
}
public E next() {
checkForComodification();
// do logic here
pos ++;
return getElement();
}
Open JDK中 ArrayList中循环遍历的类似代码实现
int cursor = 0;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
// do logic here
cursor ++;
return getElement();
}
通过上面两张类似代码实现,我们能看出两者hasNext()实现不一样。通常,我们在对ArrayList进行循环遍历时,实际上,先调用了者hasNext()判断List是否还有元素,在调用next()获取下一个元素。并且在next()方法中,会优先判断List是否被修改。
现在假设,List中只有一个元素,并且在第一次循环时,该元素被删除。我们推理一下,IBM 和Open JDK两边的行为。
IBM JDK:
第一次调用hasNext()判断List中还有元素。
第一次调用next()获取元素
并且在该步骤将元素删除后;此时 pos 数值已经变成 -1 + 1 = 0,size() = 0.
第二次调用用hasNext()判断是否List中还有元素。
Pos + 1 < size() 等同于 0 + 1 < 0, 返回false。则不进行第二次调用next()获取元素。
Open JDK:
第一次调用hasNext()判断List中还有元素。
第一次调用next()获取元素
并且在该步骤将元素删除后;此时 cursor 数值已经变成 0 + 1 = 1,size() = 0.
第二次调用hasNext()判断是否List中还有元素。
cursor != size() 等同于 1 != 0, 返true。
第二次调用next()获取元素,发现List中的元素发生改动,抛出ConcurrentModificationException
在这个假设出来后,我们做了两步验证,
第一步,编写测试类,对只包含一个元素的ArrayList循环时,进行删除操作。IBM JDK ArrayList正常遍历结束,Open JDK ArrayList遍历时抛出出ConcurrentModificationException
第二步,将业务代码中的的ArrayList类型 变量statusTypes打印出来,证实了该List确实只包含1个元素。
至此, ConcurrentModificationException 谜团已经解开。IBM 和 Open JDK实现不同导致了在for-each循环时进行删除操作,会有不同的表现。
解决方案
在已经明确原因情况下,我们可以放心地复写用户业务代码,修改变量statusTypes循环方式即可解决这个问题。
修改后的业务伪代码
if(isCaseFlagl){
//do business logic
}else if(isCaseFlag2){
List<String> statusTypes = getStatusTypes();
Iterato r<St ring> iterator = statusTypes.iterator();
while(iterator.hasNext()){
String statusType = iterator.next();
if(isMatchDeletedCondition(statusType)){
iterator.remove();
}
}
浙公网安备 33010602011771号