首页 > 其他分享 >并发容器之CopyOnWrite

并发容器之CopyOnWrite

时间:2024-03-24 13:58:57浏览次数:21  
标签:index CopyOnWrite 容器 lock 并发 复制 数据

CopyOnWrite容器

什么是CopyOnWrite容器呢?CopyOnWrite容器是一个写时复制的容器。在向容器中添加元素时,不会直接向当前容器中添加,而是将当前容器进行copy,复制出一个新的容器,然后往新的容器中添加元素,添加完元素之后,再将容器的引用指向新的容器。使得我们可以对CopyOnWrite容器进行并发的读而不需要加锁,采用了读写分离的思想,写时复制的策略

使用的场景是读多写少的时候使用,如redis、Linux的文件管理系统等

基本思路

  • 当读取共享数据时,直接读取,不需要有其他操作
  • 当写共享数据时,将旧数据复制出来一份作为新数据,只修改新数据,修改完新数据后将新数据的引用赋值给原来数据的引用,在写数据的过程中,所有读取共享数据都是读的旧数据

以CopyOnWriteArrayList为例

CopyOnWriteArrayList

CopyOnWriteArrayList是同步List的并发替代品,是java并发包java.util.concurrent中提供的用于并发操作且线程安全的ArrayList,可以提供更好的并发性,并且避免了在迭代期间对容器加锁和复制,在每次修改的时,会创建一个新的容器拷贝,以此来实现可变性

// 存放具体的元素
private transient volatile Object[] array;
// 独占锁用来保证同时只有一个线程对array进行修改
final transient ReentrantLock lock = new ReentrantLock();

实际是对底层数组的复制操作

    public E set(int index, E element) {
      // 获取独占锁,写入时加锁,保证只有一个线程在写,防止多线程时copy多份副本
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
          // 获取array
            Object[] elements = getArray();
            E oldValue = get(elements, index);
      // 值修改时
            if (oldValue != element) {
                int len = elements.length;
               // 根据原来的数组拷贝一个新的数组
                Object[] newElements = Arrays.copyOf(elements, len);
               // 对新的数组调整赋值
                newElements[index] = element;
               // 原数组的引用指向新数组,替换掉之前的数组
                setArray(newElements);
            } else {// 值没有修改
                // Not quite a no-op; ensures volatile write semantics
                setArray(elements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }

每次容器改变对于基础数组的复制也是有一定开销的,特别是当容器较大时,所以该种方式比较适合于读取操作的次数远大于修改操作的次数时才适用

但是对于获取操作并不会进行加锁,而是直接进行获取

final Object[] getArray() {
  return array;
}

public E get(int index) {
    return get(getArray(), index);
}

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

所以可能在进行读取的时候获取到的数据并不准确,这是写时复制策略产生的弱一致性问题

优缺点

优点
  • 效率高,读写操作不是同一份数据,在进行读和写时不需要阻塞其他来读取的线程
  • 保证最终一致性,读和写操作的不是同一份数据,可以保证读数据的操作不会读到写了一半的数据
缺点
  • 数据实时性差,在写操作完成之前之前都是读取旧数据
  • 内存占用大,有复制操作,将旧数据复制出来一份作为新数据,会占用两份内存,以时间换空间

https://zhhll.icu/2021/多线程/并发容器/2.CopyOnWrite容器/

本文由 mdnice 多平台发布

标签:index,CopyOnWrite,容器,lock,并发,复制,数据
From: https://blog.csdn.net/Lxn2zh/article/details/136986811

相关文章

  • Java面试题:用Java并发工具类,实现一个线程安全的单例模式;使用Java并发工具包和并发框架
    面试题一:设计一个Java并发工具类,实现一个线程安全的单例模式,并说明其工作原理。题目描述:请设计一个Java并发工具类,实现一个线程安全的单例模式。要求使用Java内存模型、原子操作、以及Java并发工具包中的相关工具。考察重点:对Java内存模型的理解。对Java并发工具包的了......
  • 【Docker】Airflow 容器化部署
    Airflow环境标准软件基于BitnamiAirflow构建。当前版本为2.8.2你可以通过轻云UC部署工具直接安装部署,也可以手动按如下文档操作,该项目已经全面开源,可以从如下环境获取配置文件地址:https://gitee.com/qingplus/qingcloud-platformqinghub自动安装部署配置库什么是Air......
  • 把 Windows 装进 Docker 容器里
    本篇文章聊聊如何在Docker里运行Windows操作系统,WindowsinDockerContainer(WinD)。写在前面我日常使用macOS和Ubuntu来学习和工作,但是时不时会有Windows使用的场景,不论是运行某个指定的软件,还是要做一些跨平台软件的功能验证。在去年开源 soulteary/docker-chatgp......
  • 高并发下数据幂等问题的9种解决方案
    置顶说明严格来说,所谓人云亦云的接口幂等性,大部分场景是要求接口防重或数据幂等,而不是接口幂等,很多人都搞混了。举例:后端做了支付防重,用户对单一订单重复支付,再次支付不是提示支付成功(接口幂等是要求多次请求返回的结果一致),而是提示请勿重复支付。很多时候是防重是保证MySQL表......
  • 把 Windows 装进 Docker 容器里
    本篇文章聊聊如何在Docker里运行Windows操作系统,WindowsinDockerContainer(WinD)。写在前面我日常使用macOS和Ubuntu来学习和工作,但是时不时会有Windows使用的场景,不论是运行某个指定的软件,还是要做一些跨平台软件的功能验证。在去年开源 soulteary/docker-chatgp......
  • 【面试】高并发中的集合
    本文旨在总结多线程情况下集合的使用Java中的集合大致以下三个时期:第一代线程安全集合类以Vector、HashTable为代表的初代集合,使用synchronized在修饰方法,从而保证线程安全。缺点:效率低。代码示例Vectoradd方法源码/***Appendsthespecifiedelementtotheendoft......
  • 26.C++ STL常用容器—deque
    如果想单独一对一辅导学习C++、Java、Python编程语言的可以加微信咨询3.3deque容器3.3.1deque容器基本概念功能:双端数组,可以对头端进行插入删除操作deque与vector区别:vector对于头部的插入删除效率低,数据量越大,效率越低deque相对而言,对头部的插入删除速度回比v......
  • Docker最有价值的“云”(容器级虚拟化)Docker安装
    Docker,封装我们的应用。(比如Apache、php、MySQL等。)被Docker封装的应用,会变成Docker里面的一个集装箱。这个集装箱只要分享,一打开就是已经安装好的环境。我这里是跟上一篇文章连着的,但是大家的虚拟机要是刚创建的,没有进行任何环境的安装的话,不用进行【还原快照】这一步......
  • 关于并发编程一些问题与解决--事务回滚@Transactional
    先贴一下代码吧@Transactional@OverridepublicintupSunp(Integera_id){//查询数据库QueryWrapper<Animal>animalQueryWrapper=newQueryWrapper<>();animalQueryWrapper.eq("a_id",a_id);Animalanima......
  • uniapp开发ios,scroll-view横向滚动失效,动态获取scroll-view内部子容器总宽度,然后添加
    这是老bug了,官方一直没有解决掉。已经摸索到完美解决方案,遇到这个问题的可以看下。本文以三级导航页面中的二级横向滚动导航为例,说明如何做到不同宽度子元素的横向滚动。bug定位:本来横向滚动只要子元素宽度大于scroll-view固定宽度就可以滚动的,但是IOSApp开发中子元素高度必须......