同步类容器是一种串行化、线程安全的容器,在特定情况下对资源加锁。因此在多线程环境中,会降低应用的吞吐量,另外,同步容器类在早期设计时没有考虑一些并发问题,因此在使用时经常会出现 ConcurrentModificationException 等并发异常。
| <br>同步类容器<br> | <br>并发类容器<br> |
| <br>HashTable<br> | <br>ConcurrentHashMap<br> |
| <br>Vector<br> | <br>CopyOnWriteArrayList<br> |
| <br>Stack<br> | <br>CopyOnWriteArraySet<br> |
package concurrent;
import java.util.Iterator;
import java.util.Vector;
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
Vector<String> names = new Vector<>();//1.5
names.add("zs");
names.add("ls");
names.add("ww");
Iterator<String> iter = names.iterator();
while (iter.hasNext()) {
System.out.println(iter.next()); // 仅仅对集合进行读操作,不会有异常
names.add("x"); //仅仅对集合进行写操作:因为ArrayList会动态扩容(1.5倍),因此names会无限扩大,因此会报错
}
}
}
zs
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.Vector$Itr.checkForComodification(Vector.java:1210)
at java.util.Vector$Itr.next(Vector.java:1163)
at concurrent.TestCopyOnWriteArrayList.main(TestCopyOnWriteArrayList.java:24)
如果将 Vector 修改为 ArrayList,也会报 ConcurrentModificationException 异常。
造成 ConcurrentModificationException 异常的原因有以下几点。
a ArrayList 有一个全局变量 modCount(从父类 AbstractList 继承而来),并且在 ArrayList 内部类 ITR 中有一个 expectedModCount 变量。
b 当对 ArrayList 进行迭代(iter.next)时,迭代器会先确保 modCount 和 expectedModCount 的值一致,如果不一致就会抛出 ConcurrentModificationException 异常。
c 在本案例中,在迭代的同时,又进行了写操作(name.add(...)),而写操作会改变 modCount 的值(modCount++),因此就导致 modCount != expectedModCount ,最终抛出 ConcurrentModificationException 异常。
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
protected transient int modCount = 0;
}
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
}
private class Itr implements Iterator<E> {
int expectedModCount = modCount;
public E next() {
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
一种解决 ConcurrentModificationException 异常的简单方法,就是将之前使用的同步类容器,改为并发类容器。
JUC 提供了多种并发类容器来改善性能,并且也解决了上述异常。因此,如果在多线程环境下编程,建议使用并发类容器来替代传统的同步类容器。
package concurrent;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
CopyOnWriteArrayList<String> names = new CopyOnWriteArrayList<>();
names.add("zs");
names.add("ls");
names.add("ww");
Iterator<String> iter = names.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
names.add("x");
}
}
}
zs
ls
ww
CopyOnWrite 容器增加元素,会经历以下两步。
1 先将当前容器复制一份,然后向新的容器(复制后的容器)里添加元素(并不会直接向当前容器增加元素)。
2 增加完元素后,再将引用指向新的容器,原容器等待被 GC 收集。
对于“读多写少”的业务,更适合使用 CopyOnWrite 容器,但如果是“写多读少”就不适合,因为容器复制比较消耗性能。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/chengqiuming/article/details/123599847
内容来源于网络,如有侵权,请联系作者删除!