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();
	}
}

  

 

 


posted @ 2021-04-20 12:58  程序员札记  阅读(175)  评论(0)    收藏  举报