CopyOnWriteArrayList

1. 并发容器的概述

对于ArrayList来说是线程不安全的,为了解决这个问题,JDK为我们提供了线程安全的并发容器

Vector 过时的同步容器

Vector是早期为了解决ArrayList的线程不安全而实现的,它的实现方法很简单,在ArrayList的方法上都加上了Synchronized
在这里插入图片描述
既然放在方法上锁住的是整个实例对象,可想而知这样的效率很低

Collections.synchronizedList()
List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());

Collections.synchronizedList()为ArrayList加上了一层包装使其变成了线程安全的,我们来看看他的源码

     * @param  <T> the class of the objects in the list
     * @param  list the list to be "wrapped" in a synchronized list.
     * @return a synchronized view of the specified list.
     */
    public static <T> List<T> synchronizedList(List<T> list) {
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list) :
                new SynchronizedList<>(list));
    }

可以看到它是根据传入的参数是否实现了RandomAccess接口而决定返回SynchronizedRandomAccessList还是SynchronizedList;我们的ArrayList是实现了这个接口的,因为底层是数组,能被随机访问;自然返回的是前缀者,我们来看SynchronizedRandomAccessList

    /**
     * @serial include
     */
    static class SynchronizedRandomAccessList<E>
        extends SynchronizedList<E>
        implements RandomAccess {

        SynchronizedRandomAccessList(List<E> list) {
            super(list);
        }
        .......
    }

可以看到他继承了SynchronizedList,我们进到里面看看,他是使用的同步代码块的方法实现同步,可以发现它的效率和vector差不多
在这里插入图片描述

CopyOnWriteArrayList

用来代替VectorsynchronizedList
VectorsynchronizedList的锁的粒度太大,并发效率低,而且在迭代的时候是无法编辑的(fail-fast)
适用于读操作尽可能,写操作慢一点没关系,也就是读多写少的操作

2. CopyOnWriteArrayList

就像上面说的CopyOnWriteArrayList用来代替VectorsynchronizedList,主要解决下面的问题

  • VectorsynchronizedList的锁的粒度太大,并发效率低
  • 而且在迭代的时候是无法编辑的(fail-fast)

在说 CopyOnWriteArrayList之前先回顾一下读写锁,对于读写锁读读共享,其他的都互斥的
而对于CopyOnWriteArrayList对于读写锁更升了一级,读操作完全不加锁,写入也不会阻塞读操作,只要写入和写入之间才会需要同步等待

我们先来看一下它的迭代时的编辑

对于ArrayList来说,如果在迭代的过程中对集合修改,会发生fail-fast

public class CollectionDemo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()){
            System.out.println(list);
            Integer next = iterator.next();
            System.out.println(next);
            if(next == 2){
                list.remove(3);
            }
        }
    }
}
/*[1, 2, 3, 4]
        1
        [1, 2, 3, 4]
        2
        [1, 2, 3]
        Exception in thread "main" java.util.ConcurrentModificationException
        at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042)
        at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)
        at com.minifull.day_03.CollectionDemo.main(CollectionDemo.java:17)*/

但是对于CopyOnWriteArrayList

public class CollectionDemo {
    public static void main(String[] args) {
        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()){
            System.out.println(list);
            Integer next = iterator.next();
            System.out.println(next);
            if(next == 2){
                list.remove(3);
            }
        }
    }
}
/*
[1, 2, 3, 4]
1
[1, 2, 3, 4]
2
[1, 2, 3]
3
[1, 2, 3]
4*/

可以看到没有报错,而且就算我们删除了4,在之后还是打印了出来4,也就是说对于我们的改变好像一定感知都没有,拿着的一直都是之前没有改变过的那份内容

思考一下他的设计理念

CopyOnWrite的意思就是我对计算机中的某个东西进行修改我不是直接对原内存进行修改,而是把他拷贝一份放到我自己的内存,在我自己的内存进行修改,修改完再让指向原内存的指针指向我修改完的内存;

对于CopyOnWriteArrayList也是在写的时候copy一份新的出来再新的中进行修改,完成后改变指针的地址,这样就能保证读一直不被阻塞,效率很高;
我们再回想一下它的适用场所:适用于读操作尽可能,写操作慢一点没关系,也就是读多写少的操作,这样想就很明了

一句话概括:创建新副本,读写分离

所以可以在迭代的时候进行更新,但是会出现数据的过期

想想缺点

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

② 内存占用问题:
因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存中会驻扎两个对象的内存

3. 看看源码

成员属性

首先他说一个List,底层使用数组实现的,为了实现同步,内部使用的是ReentrantLock

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    /** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

    .....
add

像我们上文说的,CopyOnWrite的思想就是创建新副本,读写分离,我们来看它的add操作,的确是这样的

    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();//接收
        }
    }
get
    public E get(int index) {
        return get(getArray(), index);
    }
    @SuppressWarnings("unchecked")
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

看源码get方法的确不会发生阻塞

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
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不保证元素的顺序性,因为在写操作时会创建新的底层数组。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值