欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

解决--java.util.ConcurrentModificationException

程序员文章站 2022-06-23 12:50:04
...

一直都说ArrayList是线程不安全的,只知其话,不懂其意,今天终于碰到了


举例如下:

单线程情况下:—>运行正常

public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
                list.add(UUID.randomUUID().toString().substring(0, 6));//加入字符串
                System.out.println(list);//读取list
        }
    }

线程不安全,那么好,将ArrayList放入多线程中

举例如下:

多线程情况下:—>运行出异常

public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 6));
                System.out.println(list);
            }, "A").start();
        }
    }

如下错误:并发修改异常
解决--java.util.ConcurrentModificationException


原因分析

并发修改异常,指的是在多线程情况下线程争抢同一资源时读写出错。 例如:大巴车开车和顾客上车,大巴车行驶时是不能上乘客的,上乘客的时候大巴车不允许开车,当同时开车并同时上乘客就会出现危险。

查看ArrayList源码如下:
解决--java.util.ConcurrentModificationException


解决思路

  • 既然 ArrayList 是线程不安全的,那就使用线程安全的集合,例如使用 Vector
  • 可不可以让 ArrayList 变成线程安全的
  • 既然是读写时出现的错误,是不是可以让读写分开(这一点值得思考 --> 写时复制技术)
解决方案一:使用线程安全的集合
    public static void main(String[] args) {
//
        List<String> list = new Vector<>();
//     
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 6));
                System.out.println(list);
            }, "A").start();
        }
    }

解决方案二:将ArrayList变成线程安全
    public static void main(String[] args) {
//    
        List<String> list = Collections.synchronizedList(new ArrayList<>());
//            
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 6));
                System.out.println(list);
            }, "A").start();
        }
    }
解决方案三:读写分离–>写时复制技术
    public static void main(String[] args) {
//
        List<String> list = new CopyOnWriteArrayList<>();
//
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 6));
                System.out.println(list);
            }, "A").start();
        }
    }

写时复制技术

1.什么是写时复制技术?

写时复制是指: 在并发访问的情景下,当需要修改JAVA中Containers的元素时,不直接修改该容器,而是先复制一份副本,在副本上进行修改。修改完成之后,将指向原来容器的引用指向新的容器(副本容器)。

2.写时复制带来的影响

① 由于不会修改原始容器,只修改副本容器。因此,可以对原始容器进行并发地读。其次,实现了读操作与写操作的分离,读操作发生在原始容器上,写操作发生在副本容器上。

② 数据一致性问题:读操作的线程可能不会立即读取到新修改的数据,因为修改操作发生在副本上。但最终修改操作会完成并更新容器,因此这是最终一致性。