ArrayList
ArrayList是我们在开发中经常使用的一种集合,具有效率高并发性能强的特点。但是它是线程不安全的,会在高并发的情况下发生异常。
模拟线程不安全情况
public static void main(String[] args) {
List<String> list= new ArrayList<>();
//创建10个线程同时对ArrayList集合进行读和写操作
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,6));
System.out.println(list);},String.valueOf((i))).start();
}
}
会报出java.util.ConcurrentModificationException并发修改异常。
导致原因
1.at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
//ArrayList发生异常的源代码
final void checkForComodification() {
//modCount实际修改的值,expectedModCount期望值
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
每次的调用add方法之后modCount的值都会发生改变,而expectedModCount期望值应该与实际操作值modCount相等,如果不相等就会抛出ConcurrentModificationException()并发修改异常。
2.at java.util.ArrayList$Itr.next(ArrayList.java:851)
//发生异常部分源代码
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];
}
3.at java.util.AbstractCollection.toString(AbstractCollection.java:461)
//ArrayList发生异常的源代码
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
toString在迭代的过程中读写不一致。
总结
1.ArrayList在add()方法中没有加synchronized同步锁。
//ArrayList添加方法源代码
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
2.toString方法读写不一致(期望值与实际操作值不一致)
解决方法
- Vector集合(不建议)线程安全的,因为加了synchronized同步锁。
- 使用集合工具类Collections.synchronizedList();
- CopyOnWriteArrayList<>()写时复制集合。
CopyOnWriteArrayList 写时复制
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行Copy, 复制出一个新的容器Object[] newElements,然后新的容器Object[] newElements里添加元素,添加完元素之后, 再将原容器的引用指向新的容器 setArray(newElements);。这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。