0


并发安全之-CopyOnWriteArrayList

🌺个人主页:杨永杰825_Spring,Mysql,多线程-CSDN博客

🎉相关链接:ArrayList介绍-CSDN博客
每日一句:成为架构师路途遥远

📢欢迎大家:关注🔍**+点赞👍+评论📝+**收藏⭐️

前言

  • ArrayList是线程不安全的数据结构,这意味着当多个线程同时访问或修改ArrayList时,可能会导致数据一致性的问题。
  • 当多个线程同时对ArrayList进行写操作(例如添加、删除、修改元素),可能会导致其中一个线程的操作被覆盖或丢失。这是因为ArrayList在进行修改操作时并没有进行同步处理,因此多个线程之间的操作顺序是不确定的。

不安全案例

下面是一个示例,展示了当多个线程同时访问一个ArrayList时可能出现的线程不安全问题:

import java.util.ArrayList;
import java.util.List;

public class ArrayListThreadUnsafeExample {
    public static void main(String[] args) {
        // 创建一个空的ArrayList
        List<Integer> arrayList = new ArrayList<>();

        // 创建两个线程,同时向ArrayList中添加元素
        Thread thread1 = new Thread(new AddElementTask(arrayList));
        Thread thread2 = new Thread(new AddElementTask(arrayList));

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("ArrayList size: " + arrayList.size());
    }

    // 添加元素的任务
    static class AddElementTask implements Runnable {
        private List<Integer> arrayList;

        public AddElementTask(List<Integer> arrayList) {
            this.arrayList = arrayList;
        }

        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                // 向ArrayList中添加元素
                arrayList.add(i);
            }
        }
    }
}

结果:

在上述示例中,我们创建了一个空的ArrayList,并启动两个线程分别向该ArrayList中添加1000个元素。由于ArrayList是线程不安全的,因此可能出现以下两种情况之一:

  1. 在添加元素的过程中,其中一个线程的操作被覆盖或丢失,导致最终ArrayList的大小小于2000。
  2. 在添加元素的过程中,两个线程同时修改ArrayList,导致其中一个线程的操作被覆盖或丢失,最终ArrayList的大小可能大于2000。

因此,使用多个线程同时对ArrayList进行操作时,不能保证最终结果的正确性和一致性。为了解决这个问题,可以使用线程安全的替代类(如Vector或CopyOnWriteArrayList)或者使用同步机制来保证线程安全。

CopyOnWriteArrayList

CopyOnWriteArrayList是Java集合框架中的一个线程安全的List实现类。它通过对底层数组进行复制来实现线程安全,因此在迭代操作时,不需要进行额外的同步措施。

特点

  1. 线程安全:CopyOnWriteArrayList通过加锁的方式实现线程安全。在多线程环境下,可以同时进行读操作,只有在写操作时才需要进行加锁。
  2. 读写分离:CopyOnWriteArrayList通过对底层数组进行复制,在写操作时复制一份新的数组,读操作则直接在原数组上进行。这样做的好处是,读操作不需要加锁,不会阻塞其他读操作,提高了并发性能。但是写操作需要加锁,并且写操作期间对原数组的读操作会被阻塞。
  3. 弱一致性:由于CopyOnWriteArrayList在写操作时是复制一份新的数组,因此在写操作期间,读操作可能会读取到旧的数据。所以CopyOnWriteArrayList提供的是弱一致性的读写一致性。
  4. 适合读多写少的场景:CopyOnWriteArrayList适用于读多写少的场景,例如:日志系统、缓存等。

常用方法

  • add(E e):向列表末尾添加元素。
  • remove(int index):移除指定索引处的元素。
  • get(int index):获取指定索引处的元素。
  • size():获取列表的大小。

总结:CopyOnWriteArrayList是一个线程安全的List实现类,通过读写分离和对底层数组进行复制来实现线程安全。它适用于读多写少的场景,并且提供了弱一致性的读写一致性。

案例

以下是一个示例代码,展示了如何使用CopyOnWriteArrayList类:

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListExample {
    public static void main(String[] args) {
        // 创建一个CopyOnWriteArrayList
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

        // 向列表中添加元素
        list.add("A");
        list.add("B");
        list.add("C");

        // 创建一个线程,在迭代过程中添加元素
        Thread thread = new Thread(() -> {
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                String element = iterator.next();
                System.out.println(element); // 输出当前迭代的元素
                if (element.equals("B")) {
                    list.add("D"); // 在迭代过程中添加元素
                }
            }
        });

        thread.start();

        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(list);
    }
}

在上述示例中,我们创建了一个CopyOnWriteArrayList并向其添加了三个元素。然后我们创建了一个线程,在迭代CopyOnWriteArrayList的过程中添加了一个新元素。CopyOnWriteArrayList是线程安全的,因此在迭代过程中添加元素不会导致ConcurrentModificationException异常。

输出结果可能是:

A
B
C
[A, B, C, D]

可以看到,即使在迭代过程中添加了元素,最终的CopyOnWriteArrayList仍然包含了新添加的元素。这是因为CopyOnWriteArrayList在添加和修改操作时,会创建一个新的拷贝并进行操作,从而保证了原始数据的线程安全性。

底层原理

CopyOnWriteArrayList的底层原理是通过使用一个可变的数组来存储数据。在对数组进行修改(添加/删除)操作时,并不会直接修改原始数组,而是创建一个新的副本,并在副本上进行修改。这意味着对数组进行修改不会影响到正在进行迭代的线程,因此可以实现并发安全性。

具体实现步骤如下:

  1. 初始化时,CopyOnWriteArrayList会创建一个空数组,用于存储元素。
  2. 当添加元素时,CopyOnWriteArrayList会创建一个当前数组长度 + 1 大小的新数组,并将原始数组中的所有元素复制到新数组中,然后再将新元素添加到新数组的最后。
  3. 当删除元素时,CopyOnWriteArrayList会创建一个当前数组长度 - 1 大小的新数组,并将原始数组中除了要删除的元素之外的其他元素复制到新数组中。

通过这种方式,在对数组进行修改时,不会改变原始数组,而是创建一个新的副本,从而实现了并发安全性。同时,通过使用读写分离的思想,读操作可以并发进行,不需要加锁,提高了读操作的性能。

需要注意的是,由于每次修改操作都会创建一个新的数组副本,因此CopyOnWriteArrayList的内存消耗较高,适用于读操作较多而写操作较少的场景。

标签: java jvm 开发语言

本文转载自: https://blog.csdn.net/qq_31536117/article/details/135132260
版权归原作者 Java张俊华 所有, 如有侵权,请联系我们删除。

“并发安全之-CopyOnWriteArrayList”的评论:

还没有评论