zoukankan      html  css  js  c++  java
  • 多线程并发设计模式(二十)Copy-on-Write模式

      Java 里 String 这个类在实现 replace() 方法的时候,并没有更改原字符串里面 value[]数组的内容,而是创建了一个新字符串,这种方法在解决不可变对象的修改问题时经常用到。如果你深入地思考这个方法,你会发现它本质上是一种 Copy-on-Write 方法。所谓 Copy-on-Write,经常被缩写为 COW 或者 CoW,顾名思义就是写时复制。

      不可变对象的写操作往往都是使用 Copy-on-Write 方法解决的,当然 Copy-on-Write 的应用领域并不局限于 Immutability 模式。

      CopyOnWriteArrayList 和 CopyOnWriteArraySet 这两个 Copy-on-Write 容器,它们背后的设计思想就是 Copy-on-Write;通过 Copy-on-Write 这两个容器实现的读操作是无锁的,由于无锁,所以将读操作的性能发挥到了极致。

      类 Unix 的操作系统中创建进程的 API 是 fork(),传统的 fork() 函数会创建父进程的一个完整副本,例如父进程的地址空间现在用到了 1G 的内存,那么 fork() 子进程的时候要复制父进程整个进程的地址空间(占有 1G 内存)给子进程,这个过程是很耗时的。而 Linux 中的 fork() 函数就聪明得多了,fork() 子进程的时候,并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间;只用在父进程或者子进程需要写入的时候才会复制地址空间,从而使父子进程拥有各自的地址空间。

      本质上来讲,父子进程的地址空间以及数据都是要隔离的,使用 Copy-on-Write 更多地体现的是一种延时策略,只有在真正需要复制的时候才复制,而不是提前复制好,同时 Copy-on-Write 还支持按需复制,所以 Copy-on-Write 在操作系统领域是能够提升性能的。相比较而言,Java 提供的 Copy-on-Write 容器,由于在修改的同时会复制整个容器,所以在提升读操作性能的同时,是以内存复制为代价的。这里你会发现,同样是应用 Copy-on-Write,不同的场景,对性能的影响是不同的。

      Copy-on-Write 是一项非常通用的技术方案,在很多领域都有着广泛的应用。不过,它也有缺点的,那就是消耗内存,每次修改都需要复制一个新的对象出来,好在随着自动垃圾回收(GC)算法的成熟以及硬件的发展,这种内存消耗已经渐渐可以接受了。所以在实际工作中,如果写操作非常少,那你就可以尝试用一下 Copy-on-Write,效果还是不错的。

      除了上面我们说的 Java 领域、操作系统领域,很多其他领域也都能看到 Copy-on-Write 的身影:Docker 容器镜像的设计是 Copy-on-Write,甚至分布式源码管理系统 Git 背后的设计思想都有 Copy-on-Write……

    设计参考代码,注意Add和Remove方法

    //路由信息
    public final class Router{
      private final String  ip;
      private final Integer port;
      private final String  iface;
      //构造函数
      public Router(String ip, 
          Integer port, String iface){
        this.ip = ip;
        this.port = port;
        this.iface = iface;
      }
      //重写equals方法
      public boolean equals(Object obj){
        if (obj instanceof Router) {
          Router r = (Router)obj;
          return iface.equals(r.iface) &&
                 ip.equals(r.ip) &&
                 port.equals(r.port);
        }
        return false;
      }
      public int hashCode() {
        //省略hashCode相关代码
      }
    }
    //路由表信息
    public class RouterTable {
      //Key:接口名
      //Value:路由集合
      ConcurrentHashMap<String, CopyOnWriteArraySet<Router>> 
        rt = new ConcurrentHashMap<>();
      //根据接口名获取路由表
      public Set<Router> get(String iface){
        return rt.get(iface);
      }
      //删除路由
      public void remove(Router router) {
        Set<Router> set=rt.get(router.iface);
        if (set != null) {
          set.remove(router);
        }
      }
      //增加路由
      public void add(Router router) {
        Set<Router> set = rt.computeIfAbsent(
          route.iface, r -> 
            new CopyOnWriteArraySet<>());
        set.add(router);
      }
    }
    

      

  • 相关阅读:
    java-五子棋棋盘排布 && 数字转换成汉字 &&大数计算
    动手动脑,无法自拔(2)课时作业4
    动手动脑,无法自拔(1)课时作业3
    《大道至简》 第二章 读后感
    NABCD需求
    第九周总结
    第八周总结
    最大子数组(循环连接)
    第七周总结
    最大子数组和
  • 原文地址:https://www.cnblogs.com/SmartCat994/p/14236778.html
Copyright © 2011-2022 走看看