这里我们不讲ArrayList是怎么实现的,怎么扩容的?这次主讲的是在操作ArrayList时出现的ConcurrentModificationException!
首先我们看下面这段代码的三个方法分别ordinaryCycle(),enhancedCycle(),iteratorCycle()运行main函数中的个方法
private static void ordinaryCycle() {//普通for循环
ArrayList<Integer> list = new ArrayList<>();
list.add(2);
for (int i = 0; i < list.size(); i++) {
if (list.get(i) == 2) {
list.remove(list.get(i));
}
}
}
private static void enhancedCycle() {//增强for循环
ArrayList<Integer> list = new ArrayList<>();
list.add(2);
for (Integer integer : list) {
if (integer == 2) {
list.remove(integer);
}
}
}
private static void iteratorCycle() {//迭代器
ArrayList<Integer> list = new ArrayList<>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
if (integer == 2) {
list.remove(integer);
}
}
}
分别运行这三个方法得到只有ordinaryCycle()这个方法是正常运行结束了,其他两个都是报ConcurrentModificationException异常。这个是为什么呢?
我们分析通过iteratorCycle()这个方法的运行流程来分析原因。
①:查看 Iterator<Integer> iterator = list.iterator();这步在源码里
public Iterator<E> iterator() {
return new Itr();
}
得到new Itr()对象 Itr 是ArrayList的一个内部类,Itr这个类里有三个字段:
- int cursor; // 表示下一个要访问的元素的索引,从next()方法的具体实现就可看出
- int lastRet = -1; // 表示上一个访问的元素的索引
- int expectedModCount = modCount;//表示对ArrayList修改次数的期望值,它的初始值为modCount,modCounts是AbstractList的成员变量,通过ArrayList源码中add()和remove()方法可以得到modCounts是记录ArrayList元素修改(新增,移除)的次数(每次+1)
②:之后继续执行iterator.hasNext()来进行循环添加,这一步好懂就是看容器里还有没有数据了
③:这里着重看iterator.next()这步,进入Itr()可以看到next()方法
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];
}
next()第一步就是执行checkForComodification();方法
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
这里会判断modCount和expectedModCount是否相等,不等就抛出异常,
④:当代码执行到list.remove(list.get(i));时会对modCount++, 再次执行next时执行checkForComodification发现modCount和expectedModCount不相等。这时候就抛出ConcurrentModificationException
⑤:这里有人问为啥enhancedCycle()也会抛出这样的异常?
这是因为增强for循环其实是Java的语法糖,代码编译后其实底层使用的就是迭代器。
大家可以编译后看看源码是不是?
那怎么解决这个问题呢?
大家看到Itr类里面有个remove()方法
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();
}
}
可以看到这个方法里有一步expectedModCount = modCount;这样就不发生这个问题了。
说道这里其实只是单线程下解决了这个问题,那么多线程下面怎么解决呢?
我们单独运行这个multithreadUnsafeRemove()
private static void multithreadUnsafeRemove() {
ArrayList<Integer> list = new ArrayList<>();
list.add(1); list.add(2);
list.add(3);list.add(4);
Thread t1 = new Thread(() -> {
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
System.out.println(integer);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
if (integer == 1) {
iterator.remove();
System.out.println("修改成功");
}
}
});
t1.start();
t2.start();
}
发现java.util.ConcurrentModificationException
首先我们分析这个过程:(这里假设t1先与t2执行)
①:我们new了两个线程,当t2线程获得运行的时候,t2线程进行 iterator.remove();这步操做 这里主要是通过ArrayList.this.remove(lastRet);执行下面 remove()方法
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
将 modCount++;然后将expectedModCount = modCount;
②:然后t1获得运行的时候,t2对modCount和expectedModCount进行了修改,而这里重要的一点是Iterator<Integer> iterator = list.iterator()这一步new Itr()是每个线程私有的因此t2线程修改了expectedModCount。t1线程并不知道且也不会去关心这个修改,这是t1线程运行的expectedModCount还是之前t1线程得到的expectedModCount。 因此t1再执行 Iterator<Integer> iterator = list.iterator();这不是会发现expectedModCount != modCount,因此会抛出ConcurrentModificationException
③:我们上面假设了t1先与t2执行,如果t2执行完后再执行t1
这里我们将t1线程改成修改成如下的:
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(10);//睡10s保证t2先执行完
} catch (InterruptedException e) {
e.printStackTrace();
}
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
System.out.println(integer);
}
});
再执行我们会发现并没有出错,这是因为t2线程完全执行remove完后再t1线程才开始执行
这时t1第一步通过
Iterator<Integer> iterator = list.iterator();
expectedModCount 和 modCount是相等的。虽然上面的是可以避免,实实质就是单线程版的,因此如何彻底解决呢?有人说ArrayList不是线性安全的,那我们用Vector?其实我们通过查看Vector源码发现其实是和ArrayList一样的。最终要解决的我们要使用juc包下的CopyOnWriteArrayList来解决,见multithreadSafeRemove()方法
private static void multithreadSafeRemove() {
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
list.add(1);list.add(2);
list.add(3);list.add(4);
Thread t1 = new Thread(() -> {
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
System.out.println(integer);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
if (integer == 2) {
list.remove(integer);
System.out.println("修改成功");
}
}
});
t1.start();
t2.start();
}
网上有博客说是因为modCount没有用voltaile修饰导致t2线程修改了modCount导致t1线程在执行期间并没有拿到最新的modCount。这里是不对的,如果是没有拿到最新值反而是运行正确的!因为modCount是t1之前加载的,expectedModCount也是之前t1加载的,反而是可以正确运行的。可以用specialCase() 大家测试要多试几次因为这个是有偶然性的
private static void specialCase() {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);list.add(2);list.add(3);list.add(4);
for (int i = 0; i < 4; i++) {
int finalI = i;
new Thread(() -> {
if (finalI % 2 == 0) {
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
if (integer == 1) {
iterator.remove();
System.out.println("修改成功");
}
}
} else {
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
System.out.println(integer);
}
}
}).start();
}
}
至此ArrayList出现ConcurrentModificationException异常就很容易定位了。建议高并发下请使用CopyOnWriteArrayList,我们下一节来讲CopyOnWriteArrayList
源码
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @Author:pengfei
* @Description ArrayList之ConcurrentModificationException
* @Date:Created in 11:30 2019/6/27
* @Modified by
*/
public class UnSafeCollection {
public static void main(String[] args) {
ordinaryCycle();//运行正确
enhancedCycle();//java.util.ConcurrentModificationException
iteratorCycle();//java.util.ConcurrentModificationException
correctRemove();//单线程下正确解决方法
multithreadUnsafeRemove();//java.util.ConcurrentModificationException
multithreadSafeRemove();//CopyOnWriteArrayList
specialCase();//multithreadUnsafeRemove的特殊案例
}
private static void ordinaryCycle() {//普通for循环
ArrayList<Integer> list = new ArrayList<>();
list.add(2);
for (int i = 0; i < list.size(); i++) {
if (list.get(i) == 2) {
list.remove(list.get(i));
}
}
}
private static void enhancedCycle() {//增强for循环
ArrayList<Integer> list = new ArrayList<>();
list.add(2);
for (Integer integer : list) {
if (integer == 2) {
list.remove(integer);
}
}
}
private static void iteratorCycle() {//迭代器
ArrayList<Integer> list = new ArrayList<>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
if (integer == 2) {
list.remove(integer);
}
}
}
private static void correctRemove() {//正确remove
ArrayList<Integer> list = new ArrayList<>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
if (integer == 2) {
iterator.remove();
}
}
}
private static void multithreadUnsafeRemove() {//多线程不安全
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
Thread t1 = new Thread(() -> {
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
System.out.println(integer);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
if (integer == 1) {
iterator.remove();
System.out.println("修改成功");
}
}
});
t1.start();
t2.start();
}
private static void multithreadSafeRemove() {//多线程不安全
List<Integer> list = new CopyOnWriteArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
Thread t1 = new Thread(() -> {
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
System.out.println(integer);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
if (integer == 2) {
list.remove(integer);
System.out.println("修改成功");
}
}
});
t1.start();
t2.start();
}
private static void specialCase() {//多线程情况下的特例
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
for (int i = 0; i < 4; i++) {
int finalI = i;
new Thread(() -> {
if (finalI % 2 == 0) {
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
if (integer == 1) {
iterator.remove();
System.out.println("修改成功");
}
}
} else {
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
System.out.println(integer);
}
}
}).start();
}
}
}
欢迎大家在下面留言,有问题欢迎大家的积极指正。阅读这篇文章最好一步一步的通过我写的方法或者你自己想验证的方法来慢慢debug验证。