【并发设计模式】聊聊 基于Copy-on-Write模式下的CopyOnWriteArrayList

在并发编程领域,其实除了使用上一篇中的属性不可变。还有一种方式那就是针对读多写少的场景下。我们可以读不加锁,只针对于写操作进行加锁。本质上就是读写复制。读的直接读取,写的使用写一份数据的拷贝数据,然后进行写入。在将新的数据指到原来的引用上。Java中的CopyOnWriteArrayList、CopyOnWriteArraySet 都是按照COW,写时复制实现的。

    public E set(int index, E element) {
   
        // 加锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
   
            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();
        }
    }

在这里插入图片描述

Copy On Write模式

那么COW在别的领域又没有对应的应用,
其实在类Linux中,操作系统创建进程的API是fork() , 传统的fork() 会创建一个父进程的完整副本,这样暂用的地址空间就比较大,并且很耗时。linux更加聪明,那就是fork()子进程的时候,不复制整个进程的地址空间,而是让父子进程共享同一个地址空间,只用在父进程或者子进程需要写入的是才会复制地址空间,父子空间在进行隔离。

最经典的领域其实还是函数式编程领域中,通过将数据拷贝一份进行处理,然后返回结果。
COW模式的缺点是可能对于空间上比较浪费的,毕竟需要使用两倍以上的空间,是一种读多写少场景下使用。空间换时间的一种取舍。

实际应用

在实际的RPC中,客户端都是按照路由表进行查询对应服务的列表,比如A服务对应三台实例,就会将请求分发给对应的服务,按照一定的负载均衡策略。而这类进行一般来说其实都是读多写少。处分出现系统故障,恢复服务下线才会出现问题。

我们按照Map.key为服务名,value使用CopyOnWriteArraySet保存。

public class RouterTables {
   

    private static HashMap<String,CopyOnWriteArraySet<Router>> cr = new HashMap<>();

    static {
   
        CopyOnWriteArraySet<Router> userApiRouters = new CopyOnWriteArraySet<>();
        userApiRouters.add(new Router("192.1.1.1","8080","online"));
        userApiRouters.add(new Router("192.1.1.2","8080","online"));
        userApiRouters.add(new Router("192.1.1.3","8080","faild"));

        CopyOnWriteArraySet<Router> accountApiRouters = new CopyOnWriteArraySet<>();
        accountApiRouters.add(new Router("192.1.1.1","8080","online"));
        accountApiRouters.add(new Router("192.1.1.2","8080","online"));
        accountApiRouters.add(new Router("192.1.1.3","8080","faild"));

        cr.put("api.user",userApiRouters);
        cr.put("api.account",accountApiRouters);
    }

    public static void addRouter(String apiServiceName,String ip,String port,String serverStatus) {
   
        if (!cr.containsKey(apiServiceName)) {
   
            CopyOnWriteArraySet<Router> accountApiRouters = new CopyOnWriteArraySet<>();
            accountApiRouters.add(new Router(ip,port,serverStatus));
            cr.put(apiServiceName,accountApiRouters);
        } else {
   
            CopyOnWriteArraySet<Router> routers = cr.get(apiServiceName);
            if (routers.contains(new Router(ip,port,serverStatus))) {
   
                return;
            } else {
   
                routers.add(new Router(ip,port,serverStatus));
            }
        }
    }

    public static Map<String,CopyOnWriteArraySet<Router>> findRouterInfoByApiName (String apiServiceName) {
   
        return (Map<String, CopyOnWriteArraySet<Router>>) cr.get(apiServiceName);
    }

    public static void deleteRouterInfoByApiName (String apiServiceName) {
   
        if (cr.containsKey(apiServiceName)) {
   
            cr.remove(apiServiceName);
        }
    }

    public static void prinltnAllInfo() {
   
        cr.forEach((s, routers) -> System.out.println(s +"\t"+ routers));
    }

}

具体效果就是如下:

api.order	[Router{
   ip='192.1.1.1', port='8080', isOnline='online'}]
api.account	[Router{
   ip='192.1.1.1', port='8080', isOnline='online'}, Router{
   ip='192.1.1.2', port='8080', isOnline='online'}, Router{
   ip='192.1.1.3', port='8080', isOnline='faild'}]

总结

我们知道ArrayList是并发不安全的容器,如果需要在并发中使用数组集合,并且是读多写少的场景下,就非常推荐使用CopyOnWriteArrayList.
在这里插入图片描述

相关推荐

  1. 设计模式之避免共享设计模式Copy-on-Write模式

    2023-12-27 01:32:02       68 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2023-12-27 01:32:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2023-12-27 01:32:02       100 阅读
  3. 在Django里面运行非项目文件

    2023-12-27 01:32:02       82 阅读
  4. Python语言-面向对象

    2023-12-27 01:32:02       91 阅读

热门阅读

  1. PHP函数学习总结

    2023-12-27 01:32:02       75 阅读
  2. 鸿蒙 - arkTs:网络请求封装和使用

    2023-12-27 01:32:02       67 阅读
  3. 使用api-spec-converter将openapi3转为swagger2

    2023-12-27 01:32:02       56 阅读
  4. 记第一个单元测试项目

    2023-12-27 01:32:02       58 阅读
  5. 使用FFmpeg进行录屏

    2023-12-27 01:32:02       64 阅读
  6. unity实时保存物体的坐标信息txt

    2023-12-27 01:32:02       63 阅读
  7. Git配置和钩子使用

    2023-12-27 01:32:02       67 阅读
  8. 面试心得总结ing版

    2023-12-27 01:32:02       59 阅读
  9. ubuntu 守护进程 supervisor

    2023-12-27 01:32:02       61 阅读