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);
      }
    }
    

      

  • 相关阅读:
    PAT 1010. 一元多项式求导 (25)
    PAT 1009. 说反话 (20) JAVA
    PAT 1009. 说反话 (20)
    PAT 1007. 素数对猜想 (20)
    POJ 2752 Seek the Name, Seek the Fame KMP
    POJ 2406 Power Strings KMP
    ZOJ3811 Untrusted Patrol
    Codeforces Round #265 (Div. 2) 题解
    Topcoder SRM632 DIV2 解题报告
    Topcoder SRM631 DIV2 解题报告
  • 原文地址:https://www.cnblogs.com/SmartCat994/p/14236778.html
Copyright © 2011-2022 走看看