前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【原创】Java并发编程系列28 | Copy-On-Write容器

【原创】Java并发编程系列28 | Copy-On-Write容器

作者头像
java进阶架构师
发布2020-07-15 15:28:09
8130
发布2020-07-15 15:28:09
举报
文章被收录于专栏:Java进阶架构师Java进阶架构师

正文

前面两篇讲了并发编程中线程安全HashMap:ConcurrentHashMap,那么作为同样使用频率很高的List和Set,J.U.C当然也提供了相应的线程安全集合,就是Copy-On-Write容器CopyOnWriteArrayListCopyOnWriteArraySet

  1. COW设计思想
  2. 源码分析
  3. 应用场景

1. COW思想

这里的COW当然不是奶牛,而是Copy-On-Write的简称,即写时复制,是一种用于程序设计中的优化策略。

1.1 COW原理

COW的基本思路:

  • 当读取共享数据时,直接读取,不需要有其他操作(比如阻塞等待、复制等)。
  • 当写共享数据时,将旧数据复制出来一份作为新数据,只修改新数据,修改完新数据之后将新数据的引用赋值给原来数据的引用。在整个写数据的过程中,所有读取共享数据的操作都是读的旧数据。

COW容器只有写操作与写操作之间是互斥的,读读和读写都不互斥。

1.2 COW优缺点分析

优点:

  1. 效率高。因为COW保证读和写操作的不是同一份数据,共享数据在读和写时都不需要阻塞其他来读取数据的线程,所以COW有很高的效率。
  2. 保证数据一致性。因为COW保证读和写操作的不是同一份数据,读数据的操作不会读到写了一半的数据,所以能够保证数据的最终一致性。

缺点:

  1. 数据实时性差。COW在写数据完成之前一直读取旧数据,而写数据又包括复制和修改的操作,花费时间较长,导致数据实时性较差。其实COW的设计思想就是通过牺牲数据的实时性来保证数据一致性的。
  2. 内存占用大。COW中有一步复制操作,将旧数据复制出来一份作为新数据,假如旧数据本身比较大,那么新数据也要占用同样大的内存空间。类似空间换时间的思想,这里用空间换数据一致性,当然也换取了读取数据的时间。
1.3 COW应用

COW的设计思想的一些应用:内存管理(如Linux的fork()函数),数据存储(如redis),文件管理系统(如Linux的文件管理系统),软件开发(如Java的Copy-On-Write容器)。

2. 源码分析

理解了Copy-On-Write思想,CopyOnWriteArrayListCopyOnWriteArraySet的源码就很容易了。本文以CopyOnWriteArrayList源码为例来分析Copy-On-Write容器。

2.1 类结构

CopyOnWriteArrayList只有两个属性,数组array用于存储数据,重入锁lock用于写操作的同步。

代码语言:javascript
复制
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    final transient ReentrantLock lock = new ReentrantLock();
    private transient volatile Object[] array;
}
2.2 get()

get()方法获取数据,真的不用注释和讲解,最简单的代码。唯一需要注意的一点就是get()方法是没有加锁的,不需要同步,读数据线程一定不会阻塞。

代码语言:javascript
复制
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];
}
2.3 add()

代码很简单,基本过程就是按照COW思想的操作步骤:

  1. lock锁同步
  2. 旧数组复制出一个新数组
  3. 新数组添加元素
  4. 新数组引用赋给array
代码语言:javascript
复制
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();// 1. lock锁同步
    try {
        // 2. 旧数组复制出一个新数组
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 3. 新数组添加元素
        newElements[len] = e;
        // 4. 新数组引用赋给array
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();// 解锁
    }
}

3. 应用场景

Copy-On-Write并发容器用于读多写少的并发场,如商品的访问和更新,排行榜,白名单/黑名单等。

举例:一个充值排行榜的功能,排行榜会有很多人查看访问,但是只有充值之后才会修改排行榜上的数据,或者充值之后也不更新,只有每天晚上9点更新排行榜,标准的读多写少。

代码语言:javascript
复制
public class CopyOnWriteArrayListTest {
    public static CopyOnWriteArrayList<Integer> rankIds = new CopyOnWriteArrayList<Integer>();
    public static void addRankIds(int id) {
        /*
         * 获取id在rankIds中的排序,代码省略
         * 假设id应该在排行榜中的第一个
         */
        rankIds.add(0, id);
    }
}

4. 总结

Copy-On-Write并发容器处理并发问题的原理:

  • 当读取共享数据时,直接读取,不需要有其他操作(比如阻塞等待、复制等)。
  • 当写共享数据时,将旧数据复制出来一份作为新数据,只修改新数据,修改完新数据之后将新数据的引用赋值给原来数据的引用。在整个写数据的过程中,所有读取共享数据的操作都是读的旧数据。

源码的并不只在于学习编程方法,更重要的是理解源码的设计思想,能够在开发和设计中运用。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-07-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java进阶架构师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 正文
  • 1. COW思想
    • 1.1 COW原理
      • 1.2 COW优缺点分析
        • 1.3 COW应用
        • 2. 源码分析
          • 2.1 类结构
            • 2.2 get()
              • 2.3 add()
              • 3. 应用场景
              • 4. 总结
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档