【并发容器精讲二、】CopyOnWriteArrayList

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: 诞生的历史和原因- 代替Vector和SyschronizedList 就像 ConcurrentHashMap 代替了SyschronizedMap的原因一样- Vector和SyschronizedList 锁的粒度太大,并发效率较低,并且迭代时无法编辑- Copy-On-Write 并发容器还包括CopyOnWriteArraySet,用来代替同步 Set

@[toc]

1. 诞生的历史和原因

  • 代替Vector和SyschronizedList 就像 ConcurrentHashMap 代替了SyschronizedMap的原因一样
  • Vector和SyschronizedList 锁的粒度太大,并发效率较低,并且迭代时无法编辑
  • Copy-On-Write 并发容器还包括CopyOnWriteArraySet,用来代替同步 Set

2. 适用场景

  • 读操作尽可能的快一些,而写即使慢一些也没关系

3. 读写规则

  • 回顾读写锁:读读共享、其他都互斥,(写写互斥,读写互斥,写读互斥)
  • 读写锁规则的升级:读取是完全不用加锁的,并且更厉害的是写入也不会阻塞读取操作,只有写入写入才会同步等待
  • ArrayList
 public static void main(String[] args) {
        List list=new ArrayList();
        list.add("11");
        list.add("22");
        list.add("33");
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            System.out.println(list);
            Object next = iterator.next();
            System.out.println(next);
            if(next.equals("11")){
                list.remove("22");
            }
            if(next.equals("33")){
                list.add("2222");
            }
        }
}

在这里插入图片描述
CopyOnWriteArrayList

public static void main(String[] args) {
        CopyOnWriteArrayList list = new CopyOnWriteArrayList();
        list.add("11");
        list.add("22");
        list.add("33");
        list.add("44");
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(list);
            Object next = iterator.next();
            System.out.println(next);
            if (next.equals("11")) {
                list.remove("22");
            }
            if (next.equals("33")) {
                list.add("2222");
            }
        }


    }

在这里插入图片描述
通过案例我们可以发现 ArrayList 迭代修改的时候会 抛出异常 ,而CopyOnWriteArrayList 不会

4. 实现原理&源码分析

CopyOnWrite的含义

创建新副本、读写分离

不可变原理

迭代的时候

我们看下ArrayList 源码为什么会报错

@SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

点开方法,他会进行一个比较操作所以会出现异常


        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

我们看下 CopyOnWriteArrayList 源码

这是CopyOnWriteArrayList存放数据的地方,只能通过getArray获取
并且他会上锁,用的ReentrantLock


/** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();
    
 /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

初始化 :构造函数 新建个空的数组

/**
     * Sets the array.
     */
    final void setArray(Object[] a) {
        array = a;
    }

    /**
     * Creates an empty list.
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

add 方法

 /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        //先拿到锁
        lock.lock();
        try {
        //获取初始化数组
            Object[] elements = getArray();
            //获取长度 //copy出一个新的数组
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //将我们的参数添加到这个位置
            newElements[len] = e;
            //set方法添加至
            setArray(newElements);
            return true;
        } finally {
        //释放锁
            lock.unlock();
        }
    }

get方法

@SuppressWarnings("unchecked")
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

    /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        return get(getArray(), index);
    }
 public boolean hasNext() {
            return cursor < snapshot.length;
        }

        public boolean hasPrevious() {
            return cursor > 0;
        }

        @SuppressWarnings("unchecked")
        public E next() {
        //判断 true 和  false 
        //逻辑简单了许多
            if (! hasNext())
                throw new NoSuchElementException();
            return (E) snapshot[cursor++];
        }

5. 缺点

  • 数据一致性问题 :CopyOnWrite只能保证数据最终一致性的问题,不能保证数据实时一致性,所以希望写入的数据马上能读到,不要使用CopyOnWrite
  • 内存占用问题:因为CopyOnWrite的写是复制机制,所以在写的操作,所以内存中会存在2个对象

个人博客地址:http://blog.yanxiaolong.cn/

相关文章
|
2月前
|
安全 程序员 C++
C++一分钟之-C++中的并发容器
【7月更文挑战第17天】C++11引入并发容器,如`std::shared_mutex`、`std::atomic`和线程安全的集合,以解决多线程中的数据竞争和死锁。常见问题包括原子操作的误用、锁的不当使用和迭代器失效。避免陷阱的关键在于正确使用原子操作、一致的锁管理以及处理迭代器失效。通过示例展示了如何安全地使用这些工具来提升并发编程的安全性和效率。
32 1
|
2月前
|
缓存 安全 Java
Java中的并发容器:ConcurrentHashMap详解
Java中的并发容器:ConcurrentHashMap详解
|
2月前
|
安全 Java 容器
第一篇:并发容器学习开篇介绍
第一篇:并发容器学习开篇介绍
27 4
|
2月前
|
存储 安全 算法
(九)深入并发编程之并发容器:阻塞队列、写时复制容器、锁分段容器原理详谈
相信大家在学习JavaSE时都曾接触过容器这一内容,一般Java中的容器可分为四类:Map、List、Queue以及Set容器,而在使用过程中,对于ArrayList、HashMap等这类容器都是经常使用的,但问题在于这些容器在并发环境下都会存在线程安全问题。
|
4月前
|
安全 Java 容器
Java一分钟之-并发编程:并发容器(ConcurrentHashMap, CopyOnWriteArrayList)
【5月更文挑战第18天】本文探讨了Java并发编程中的`ConcurrentHashMap`和`CopyOnWriteArrayList`,两者为多线程数据共享提供高效、线程安全的解决方案。`ConcurrentHashMap`采用分段锁策略,而`CopyOnWriteArrayList`适合读多写少的场景。注意,`ConcurrentHashMap`的`forEach`需避免手动同步,且并发修改时可能导致`ConcurrentModificationException`。`CopyOnWriteArrayList`在写操作时会复制数组。理解和正确使用这些特性是优化并发性能的关键。
38 1
|
3月前
|
安全 Java 大数据
Java性能优化(七)-多线程调优-并发容器的使用
Java性能优化(七)-多线程调优-并发容器的使用
46 0
|
4月前
|
存储 缓存 安全
Golang深入浅出之-Go语言中的并发安全容器:sync.Map与sync.Pool
Go语言中的`sync.Map`和`sync.Pool`是并发安全的容器。`sync.Map`提供并发安全的键值对存储,适合快速读取和少写入的情况。注意不要直接遍历Map,应使用`Range`方法。`sync.Pool`是对象池,用于缓存可重用对象,减少内存分配。使用时需注意对象生命周期管理和容量控制。在多goroutine环境下,这两个容器能提高性能和稳定性,但需根据场景谨慎使用,避免不当操作导致的问题。
137 7
|
4月前
|
存储 算法 Java
12张图一次性搞懂高性能并发容器ConcurrentLinkedQueue
12张图一次性搞懂高性能并发容器ConcurrentLinkedQueue
|
4月前
|
存储 Java 索引
【亮剑】Java中的并发容器ConcurrentHashMap,它在JDK1.5中引入,用于替换HashTable和SynchronizedMap
【4月更文挑战第30天】本文介绍了Java中的并发容器ConcurrentHashMap,它在JDK1.5中引入,用于替换HashTable和SynchronizedMap。文章展示了创建、添加、获取、删除和遍历元素的基本用法。ConcurrentHashMap的内部实现基于分段锁,每个段是一个独立的Hash表,通过分段锁实现并发控制。每个段内部采用数组+链表/红黑树的数据结构,当冲突过多时转为红黑树优化查询。此外,它有扩容机制,当元素超过阈值时,会逐段扩容并翻倍Segment数量,以保持高性能的并发访问。
51 0
|
4月前
|
存储 安全 算法
java多线程之并发容器集合
java多线程之并发容器集合