Exception in thread “main” java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.example.container.a.TestList.main(TestList.java:16)
**很遗憾,结果并没有达到我们想要的预期效果,执行之后直接报错!抛ConcurrentModificationException异常!**
**为啥会抛这个异常呢?**
我们一起来看看,`foreach`写法实际上是对`List.iterator()` 迭代器的一种简写,因此我们可以从分析`List.iterator()` 迭代器进行入手,看看为啥会抛这个异常。
`ArrayList`类中的`Iterator`迭代器实现,源码内容:
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMTI3NDk0My0yYjIxZTlmNzlmZGU5Mjlm?x-oss-process=image/format,png)
通过代码我们发现 `Itr` 是 `ArrayList` 中定义的一个私有内部类,每次调用`next`、`remove`方法时,都会调用`checkForComodification`方法,源码如下:
/*修改次数检查/
final void checkForComodification() {
//检查List中的修改次数是否与迭代器类中的修改次数相等
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
`checkForComodification`方法,实际上是用来检查`List`中的修改次数`modCount`是否与迭代器类中的修改次数`expectedModCount`相等,如果不相等,就会抛出`ConcurrentModificationException`异常!
那么问题基本上已经清晰了,上面的运行结果之所以会抛出这个异常,就是因为`List`中的修改次数`modCount`与迭代器类中的修改次数`expectedModCount`不相同造成的!
**阅读过集合源码的朋友,可能想起`Vector`这个类,它不是 JDK 中 ArrayList 线程安全的一个版本么?**
好的,为了眼见为实,我们把`ArrayList`换成`Vector`来测试一下,代码如下:
public static void main(String[] args) {
Vector<String> list = new Vector<String>();
//模拟10个线程向list中添加内容,并且读取内容
for (int i = 0; i < 5; i++) {
final int j = i;
new Thread(new Runnable() {
@Override
public void run() {
//添加内容
list.add(j + "-j");
//读取内容
for (String str : list) {
System.out.println("内容:" + str);
}
}
}).start();
}
}
执行程序,运行结果如下:
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMTI3NDk0My1lOTBjZmI5MWEzYzk1YTcx?x-oss-process=image/format,png)
**还是一样的结果,抛异常了**,`Vector`虽然线程安全,只不过是加了`synchronized`关键字,但是迭代问题完全没有解决!
继续回到本文要介绍的 CopyOnWriteArrayList 类,我们把上面的例子,换成`CopyOnWriteArrayList`类来试试,源码内容如下:
public static void main(String[] args) {
//将ArrayList换成CopyOnWriteArrayList
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("1");
list.add("2");
list.add("1");
System.out.println("原始list元素:"+ list.toString());
//通过对象移除等于11的元素
for (String item : list) {
if("1".equals(item)) {
list.remove(item);
}
}
System.out.println("通过对象移除后的list元素:"+ list.toString());
}
执行结果如下:
原始list元素:[1, 2, 1]
通过对象移除后的list元素:[2]
**呃呵,执行成功了,没有报错!是不是很神奇~~**
当然,类似上面这样的例子有很多,比如写10个线程向`list`中添加元素读取内容,也会抛出上面那个异常,操作如下:
public static void main(String[] args) {
final List<String> list = new ArrayList<>();
//模拟10个线程向list中添加内容,并且读取内容
for (int i = 0; i < 10; i++) {
final int j = i;
new Thread(new Runnable() {
@Override
public void run() {
//添加内容
list.add(j + "-j");
//读取内容
for (String str : list) {
System.out.println("内容:" + str);
}
}
}).start();
}
}
类似的操作例子就非常多了,这里就不一一举例了。
**CopyOnWriteArrayList 实际上是 ArrayList 一个线程安全的操作类!**
从它的名字可以看出,**`CopyOnWrite` 是在写入的时候,不修改原内容,而是将原来的内容复制一份到新的数组,然后向新数组写完数据之后,再移动内存指针,将目标指向最新的位置。**
### []( )二、简介
从 JDK1.5 开始 Java 并发包里提供了两个使用`CopyOnWrite`机制实现的并发容器,分别是`CopyOnWriteArrayList`和`CopyOnWriteArraySet`。
从名字上看,`CopyOnWriteArrayList`主要针对动态数组,一个线程安全版本的 ArrayList !
而`CopyOnWriteArraySet`主要针对集,`CopyOnWriteArraySet`可以理解为`HashSet`线程安全的操作类,我们都知道`HashSet`基于散列表`HashMap`实现,**但是`CopyOnWriteArraySet`并不是基于散列表实现,而是基于`CopyOnWriteArrayList`动态数组实现!**
关于这一点,我们可以从它的源码中得出结论,部分源码内容:
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMTI3NDk0My1mNjAzYjkwZDFhMDlmMGQ2?x-oss-process=image/format,png)
从源码上可以看出,`CopyOnWriteArraySet`默认初始化的时候,实例化了`CopyOnWriteArrayList`类,`CopyOnWriteArraySet`的大部分方法,例如`add`、`remove`等方法都基于`CopyOnWriteArraySet`实现!
两者最大的不同点是,`CopyOnWriteArrayList`可以允许元素重复,而`CopyOnWriteArraySet`不允许有重复的元素!
**好了,继续来 BB 本文要介绍的`CopyOnWriteArrayList`类~~**
打开`CopyOnWriteArrayList`类的源码,内容如下:
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMTI3NDk0My1iYTk2ZThmZGUyNDk1ZTNl?x-oss-process=image/format,png)
可以看到 `CopyOnWriteArrayList` 的存储元素的数组`array`变量,使用了`volatile`关键字保证的多线程下数据可见行;同时,使用了`ReentrantLock`可重入锁对象,保证线程操作安全。
在初始化阶段,`CopyOnWriteArrayList`默认给数组初始化了一个对象,当然,初始化方法还有很多,比如如下我们经常会用到的一个初始化方法,源码内容如下:
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMTI3NDk0My00NzhmYTY2YTJkYzE1YjFh?x-oss-process=image/format,png)
这个方法,表示如果我们传入的是一个 `ArrayList`数组对象,会将对象内容复制一份到新的数组中,然后初始化进去,操作如下:
List list = new ArrayList<>();
…
//CopyOnWriteArrayList将list内容复制出来,并创建一个新的数组
CopyOnWriteArrayList copyList = new CopyOnWriteArrayList<>(list);
`CopyOnWriteArrayList`是对原数组内容进行复制再写入,那么是不是也存在多线程下操作也会发生冲突呢?
下面我们再一起来看看它的方法实现!
### []( )三、常用方法
#### []( )3.1、添加元素
`add()`方法是`CopyOnWriteArrayList`的添加元素的入口!
`CopyOnWriteArrayList`之所以能保证多线程下安全操作, `add()`方法功不可没,源码如下:
![image](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMTI3NDk0My05OTdmMmRlZjg4MGUzZmUy?x-oss-process=image/format,png)
操作步骤如下:
* 1、获得对象锁;
* 2、获取数组内容;
* 3、将原数组内容复制到新数组;
* 4、写入数据;
* 5、将array数组变量地址指向新数组;
* 6、释放对象锁;
在 Java 中,独占锁方面,有2种方式可以保证线程操作安全,一种是使用虚拟机提供的`synchronized`来保证并发安全,另一种是使用`JUC`包下的`ReentrantLock`可重入锁来保证线程操作安全。
`CopyOnWriteArrayList`使用了`ReentrantLock`这种可重入锁,保证了线程操作安全,同时数组变量`array`使用`volatile`保证多线程下数据的可见性!
其他的,还有指定下标进行添加的方法,如`add(int index, E element)`,操作类似,先找到需要添加的位置,如果是中间位置,则以添加位置为分界点,分两次进行复制,最后写入数据!
#### []( )3.2、移除元素
`remove()`方法是`CopyOnWriteArrayList`的移除元素的入口!
源码如下:
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMTI3NDk0My1kOTdhMzJkZWM2MTk5OGMw?x-oss-process=image/format,png)
操作类似添加方法,步骤如下:
* 1、获得对象锁;
### 最后
**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://codechina.csdn.net/m0_60958482/android_p7)**
**有任何问题,欢迎广大网友一起来交流,分享高阶Android学习视频资料和面试资料包~**
偷偷说一句:群里高手如云,欢迎大家加群和大佬们一起交流讨论啊!
![](https://www.icode9.com/i/ll/?i=img_convert/52067763cc619715f1a59e855a8bb752.png)
/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMTI3NDk0My1kOTdhMzJkZWM2MTk5OGMw?x-oss-process=image/format,png)
操作类似添加方法,步骤如下:
* 1、获得对象锁;
### 最后
**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://codechina.csdn.net/m0_60958482/android_p7)**
**有任何问题,欢迎广大网友一起来交流,分享高阶Android学习视频资料和面试资料包~**
偷偷说一句:群里高手如云,欢迎大家加群和大佬们一起交流讨论啊!
[外链图片转存中...(img-6KOM8ZEp-1630925558809)]