CopyOnWriteArrayList

探讨ArrayList在多线程环境下存在的线程安全问题,介绍如何通过Collections.synchronizedList和CopyOnWriteArrayList解决该问题。分析CopyOnWrite机制的工作原理、优缺点及其适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引入

ArrayList单线程下是安全的 但是多线程下存在不安全的问题

多线程下是不安全的

例如: 我们开启多个线程向arraylist添加数据

public class ListTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                list.add("a");
            System.out.println(list);
            }).start();
        }
    }
}

报出异常
在这里插入图片描述
解决这个问题我们可以使用集合工具类创建线程安全的ArrayList

  List<String> strings = Collections.synchronizedList(new ArrayList<String>());
    for (int i = 0; i < 101; i++) {
        new Thread(()->{
            strings.add("a");
            System.out.println(strings);
        }).start();
    }
}

在这里插入图片描述
但加入synchronized此时效率较低

所以我们可以使用juc包中的 CopyOnWriterArrayList

CopyOnWriterArrayList

CopyOnWriteArrayList (拷贝写数组) 是ArrayList线程安全的集合

其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。

适合场景:读多、写少的并发场景

cow思想

它不存在“扩容”的概念,每次写操作(add 、remove)都要copy一个副本,在副本的基础上修改后改变array引用,所以称为“CopyOnWrite”,因此在写操作是加锁,并且对整个list的copy操作时相当耗时的,过多的写操作不推荐使用该存储结构。

读取的方法没有加锁 写的时候才加锁 所以性能 比 vector高

我们可以从源码中看出

public E get(int index) {
    return get(getArray(), index);
}
final Object[] getArray() {
    return array;
}
private E get(Object[] a, int index) {
    return (E) a[index];
}

读操作并没有加锁 这也是效率高的原因

add源码

写操作加入了 lock锁

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();
    }
}

(注:新版本jdk1.8后改成了synchronized 因为新版本对synchronized进行了升级提升了效率)

使用示例:

public class ListTest {
    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 101; i++) {
            new Thread(()->{
                list.add("a");
                System.out.println(list);
            }).start();
        }
    }
}

在这里插入图片描述

缺点:

内存问题:CopyOnWrite的写复制机制,在进行写操作的时候,内存里会同时驻存在两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对 象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说100M左右,那么再写入50M数据进去,内存就会占用150M,那么这个时候很有可能造成频繁的minor GC和major GC。

数据一致性问题:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

CopyOnWriteArrayList一个线程安全List实现,它通过每次修改操作(添加、删除、修改)时都创建一个新的底层数组来实现线程安全性。 CopyOnWriteArrayList的特点如下: 1. 线程安全:多个线程可以同时读取CopyOnWriteArrayList的内容,而不需要额外的同步机制。这使得它非常适合在读操作远远多于写操作的场景中使用。 2. 写操作的代价较高:每次对CopyOnWriteArrayList进行写操作时,都会创建一个新的底层数组,因此写操作的代价较高。 3. 实时性较低:由于写操作会创建新的底层数组,读取操作可能会看到旧的数据,因此CopyOnWriteArrayList的实时性较低。 使用CopyOnWriteArrayList的示例代码如下: ```java import java.util.concurrent.CopyOnWriteArrayList; public class Main { public static void main(String[] args) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); list.add("Hello"); list.add("World"); for (String item : list) { System.out.println(item); } } } ``` 在上述代码中,我们创建了一个CopyOnWriteArrayList,并向其中添加了两个元素。然后使用增强for循环遍历CopyOnWriteArrayList中的元素,并打印输出。 需要注意的是,CopyOnWriteArrayList适用于读操作远远多于写操作的场景,如果写操作非常频繁,可能会导致性能问题。此外,CopyOnWriteArrayList不保证元素的顺序性,因为在写操作时会创建新的底层数组。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值