背景
在多线程环境下ArrayList是线程不安全的,所以需要使用线程安全的List,我第一时间使用的是Collections.synchronizedList(new Arraylist<>()),但是在实际使用过程中却发生了安全事件。
测试
当前存在一个类IntegerList,该类对象在多线程环境中添加元素和获取获取
public class IntegerList {
final private List<Integer> integerList;
public IntegerList(List<Integer> integerList) {
this.integerList = integerList;
}
/*
* 获取列表中小于integer的所有数
* */
public List<Integer> getLessThanInteger(Integer integer){
List<Integer> result=new ArrayList<>();
for (Integer item : integerList) {
if(item<=integer){
result.add(item);
}
}
return result;
}
private void addInteger(Integer integer){
integerList.add(integer);
}
@Override
public String toString() {
return "IntegerList{" +
"integerList=" + integerList +
'}';
}
}
使用ArrayList 线程不安全会报错ConcurrentModificationException
public static void main(String[] args) {
IntegerList integerList = new IntegerList(new ArrayList<>());
for (int i = 0; i < 100; i++) {
final int i1=i;
new Thread(()->{
//添加元素
integerList.addInteger(i1);
//获取比i1小于等于所有数
List<Integer> lessThanInteger = integerList.getLessThanInteger(i1);
}).start();
}
}
报错原因是在getLessThanInteger中使用增强for循环遍历了列表,集合的增强for循环是使用Iterator实现的,遍历过程中如果发现其他线程有修改列表就会抛异常
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8dPYg9cl-1618477707391)(47C1CB1F99614FA1AA6696CA44A418A1)]](/https://i-blog.csdnimg.cn/blog_migrate/28e6357fd31f57c69874351285ec70f3.png)
编译后Class文件
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PkJvrl42-1618477707393)(DFF14A4109E94F42A52F9DCF3C7FF503)]](/https://i-blog.csdnimg.cn/blog_migrate/a7770f37b42df9ab364c90e8af290380.png)
Iterator next()进行修改检查,如果集合被修改过,就会抛异常
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lTHEtisN-1618477707395)(518ED57269B34197A217731EDD5ACE14)]](/https://i-blog.csdnimg.cn/blog_migrate/d0fa2d346479e153789e932bd8ee4f38.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6zCsdkVw-1618477707398)(DCAEFFA4D08046C9ADE4C39AF539D9DA)]](/https://i-blog.csdnimg.cn/blog_migrate/4beb085727542ec8a37bb5b75a3bad2d.png)
使用线程安全的Collections.synchronizedList 依然会报错
public static void main(String[] args) {
IntegerList integerList = new IntegerList( Collections.synchronizedList(new ArrayList<>()));
for (int i = 0; i < 100; i++) {
final int i1=i;
new Thread(()->{
//添加元素
integerList.addInteger(i1);
//获取比i1小于等于所有数
List<Integer> lessThanInteger = integerList.getLessThanInteger(i1);
}).start();
}
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c7ExiVjR-1618477707399)(3891D0AE7E6F41C9AA0A13D41AD97AC1)]](/https://i-blog.csdnimg.cn/blog_migrate/3fce329ac2c55e5a2837a2474de3e7c6.png)
为什么线程安全的Collections.synchronizedList在多线程下还是会报错?
原因在于返回的迭代器是线程不安全的,在jdk源码里面注释说明,要用户自己使用同步来控制遍历迭代器
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z4OrqACM-1618477707400)(97159FCE7F364A3BA88A75BDC11929E8)]](/https://i-blog.csdnimg.cn/blog_migrate/24ef5e9786592ab83cce34486343cf40.png)
使用同步改造
加入同步代码块,并且锁定的对象是 integerList
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YCB3RyTo-1618477707401)(03DCE90032D34486B90E23CC8B636D38)]](/https://i-blog.csdnimg.cn/blog_migrate/80e5f9c0f83c637c0e286edc722947d8.png)
成功执行
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ju3FClNs-1618477707401)(68854CBEE4E24161B68C6A01845B93E8)]](/https://i-blog.csdnimg.cn/blog_migrate/a5b7fc4be204b16897d9efed73e9bdd0.png)
使用CopyOnWriteArrayList 完美解决迭代过程不安全问题
public static void main(String[] args) {
IntegerList integerList = new IntegerList(new CopyOnWriteArrayList<>());
for (int i = 0; i < 100; i++) {
final int i1 = i;
new Thread(() -> {
//添加元素
integerList.addInteger(i1);
//获取比i1小于等于所有数
List<Integer> lessThanInteger = integerList.getLessThanInteger(i1);
}).start();
}
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sXJZq1UW-1618477707402)(31D9827272C74CF49C1FC8CAED04C03A)]](/https://i-blog.csdnimg.cn/blog_migrate/2ecdf530868e07cbf3694fb157827953.png)
CopyOnWriteArrayList解决线程安全的原因
CopyOnWriteArrayList获取元素的时候使用的是当前数组Array的一个快照,
而修改元素的时候,会复制一份Array再进行修改,修改不会对原本的Array产生影响,修改完后会覆盖原本的Array
修改元素
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bFTmVAab-1618477707403)(472E16CCBA7145EEBADA3D9C441989A0)]](/https://i-blog.csdnimg.cn/blog_migrate/d6475497e6982e0b7dc43b8b6502f59d.png)
迭代器
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YEfWQtxS-1618477707403)(318CE8E228AC422F837B70A55CA93F39)]](/https://i-blog.csdnimg.cn/blog_migrate/154cc3ee58e9bad40745f709f1f705c1.png)
总结
- 我们在使用线程安全的List应该使用CopyOnWriteArrayList,防止在遍历迭代器过程中出现异常
- 缺点是CopyOnWriteArrayList会使用额外的内存空间,但也是值得的吧
博客探讨了在多线程环境下ArrayList的线程不安全问题,以及Collections.synchronizedList在并发操作中仍然可能导致的ConcurrentModificationException。文章解释了CopyOnWriteArrayList如何通过数组复制解决并发迭代的安全问题,虽然消耗更多内存,但能确保线程安全。最后,建议在需要遍历安全的并发场景中使用CopyOnWriteArrayList。

279

被折叠的 条评论
为什么被折叠?



