上篇文章介绍Iterator遍历ArrayList时有可能引发ConcurrentModificationException异常产生的原因是modCount和expectedModCount的值不一致,具体介绍参见 Iterator迭代器
异常解决方法
1、单线程环境
仔细观察我们会发现Iterator也提供了一个remove()方法,实质也是调用了ArrayList中的remove,源码如下:
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
从上面代码可以看出 增加了 expectedModCount = modCount; 所以不会抛出ConcurrentModificationException异常
2、多线程环境
在多线程环境下,我们再次验证上面代码
public class Test {
static List<String> list = new ArrayList<String>();
public static void main(String[] args) {
list.add("A");
list.add("B");
list.add("C");
list.add("D");
// 用于读取操作的线程
new Thread() {
public void run() {
Iterator<String> ite = list.iterator();
while (ite.hasNext()) {
String str = ite.next();
System.out.println(Thread.currentThread().getName() +
": " + str);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} ;
}.start();
// 用于删除操作的线程
new Thread() {
public void run() {
Iterator<String> ite = list.iterator();
while (ite.hasNext()) {
String str = ite.next();
if ("B".equals(str)) {
ite.remove();
}
}
};
}.start();
}
}
输出:
Thread-0: A
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at com.collection.Test$1.run(Test.java:24)
出现异常的原因是一个线程修改了list的modCount,而导致另一个线程进行迭代时modCount与该迭代器的expectedModCount不相等
解决方法1、迭代前加synchronized锁
public class Test {
static List<String> list = new ArrayList<String>();
public static void main(String[] args) {
list.add("A");
list.add("B");
list.add("C");
list.add("D");
// 用于读取操作的线程
new Thread() {
public void run() {
Iterator<String> ite = list.iterator();
synchronized (list) { // 加锁
while (ite.hasNext()) {
String str = ite.next();
System.out.println(Thread.currentThread().getName() +
": " + str);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} ;
}.start();
// 用于删除操作的线程
new Thread() {
public void run() {
Iterator<String> ite = list.iterator();
synchronized (list) { // 加锁
while (ite.hasNext()) {
String str = ite.next();
if ("B".equals(str)) {
ite.remove();
}
}
}
};
}.start();
}
}
解决方法2、使用CopyOnWriteArrayList
public class Test {
static List<String> list = new CopyOnWriteArrayList<String>();
public static void main(String[] args) {
list.add("A");
list.add("B");
list.add("C");
list.add("D");
// 用于读取操作的线程
new Thread() {
public void run() {
Iterator<String> ite = list.iterator();
while (ite.hasNext()) {
String str = ite.next();
System.out.println(Thread.currentThread().getName() +
": " + str);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} ;
}.start();
// 用于删除操作的线程
new Thread() {
public void run() {
Iterator<String> ite = list.iterator();
while (ite.hasNext()) {
String str = ite.next();
if ("B".equals(str)) {
list.remove(str);
}
}
} ;
}.start();
}
}
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/14223.html