J.U.C Review - CopyOnWrite容器

文章目录

什么是CopyOnWrite容器

CopyOnWrite容器是一种实现了写时复制(Copy-On-Write,COW)机制的并发容器。在并发场景中,多个线程可能同时访问同一资源,当某个线程需要修改数据时,系统会创建该数据的副本供其修改,而其他线程仍然可以访问原始数据。这种机制的主要优点是可以在读操作频繁的情况下,避免加锁,从而提高读取性能。

在Java中,从JDK 1.5开始,提供了两个主要的CopyOnWrite容器:CopyOnWriteArrayListCopyOnWriteArraySet。这两个容器的设计使得在"读多写少"的场景下,能够有效地提高并发性能。


CopyOnWriteArrayList

优点

  1. 无需同步CopyOnWriteArrayList在进行读取操作时不需要任何同步措施,极大地提高了读取性能。

  2. 避免异常 :在遍历时,即使有其他线程对列表进行修改,也不会抛出ConcurrentModificationException异常,因为读取和写入操作分别作用于不同的容器。

缺点

  1. 内存开销:每次写操作都会复制整个容器,导致内存使用增加,可能引发频繁的垃圾回收(GC)问题。

  2. 数据一致性问题:由于写操作和读操作作用于不同的容器,读操作可能读取到旧数据,因此不能保证实时一致性。

源码示例

CopyOnWriteArrayListaddremove方法的实现逻辑如下:

java 复制代码
public boolean add(E e) {
   
    // ReentrantLock加锁,保证线程安全
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        // 拷贝原容器,长度为原容器长度加一
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 在新副本上执行添加操作
        newElements[len] = e;
        // 将原容器引用指向新副本
        setArray(newElements);
        return true;
    } finally {
        // 解锁
        lock.unlock();
    }
}

public E remove(int index) {
        
        // 加锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0)
                // 如果要删除的是列表末端数据,拷贝前len-1个数据到新副本上,再切换引用
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                // 否则,将要删除元素之外的其他元素拷贝到新副本中,并切换引用
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        } finally {
            // 解锁
            lock.unlock();
        }
    }

再来看看CopyOnWriteArrayList效率最高的读操作的源码

java 复制代码
public E get(int index) {
    return get(getArray(), index);
}
java 复制代码
 private E get(Object[] a, int index) {
     return (E) a[index];
 }

由上可见"读操作"是没有加锁,直接读取。


仿写:CopyOnWriteMap的实现

虽然Java并发包中没有提供CopyOnWriteMap,但可以参考CopyOnWriteArrayList实现一个简单的版本。以下是一个基本的实现示例:

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class CopyOnWriteMap<K, V> implements Map<K, V> {
    private volatile Map<K, V> internalMap = new HashMap<>();

    public V put(K key, V value) {
        synchronized (this) {
            Map<K, V> newMap = new HashMap<>(internalMap);
            V oldValue = newMap.put(key, value);
            internalMap = newMap;
            return oldValue;
        }
    }

    public V get(Object key) {
        return internalMap.get(key);
    }

    public void putAll(Map<? extends K, ? extends V> m) {
        synchronized (this) {
            Map<K, V> newMap = new HashMap<>(internalMap);
            newMap.putAll(m);
            internalMap = newMap;
        }
    }
}

CopyOnWriteMap适用于"读多写少"的场景,例如一个搜索网站的黑名单管理系统。黑名单在特定时间更新,而在用户搜索时,系统需要快速检查关键字是否在黑名单中。


注意事项

使用CopyOnWrite容器时需要注意:

  1. 内存开销:应根据实际需求初始化容器的大小,以减少扩容带来的开销。

  2. 数据一致性CopyOnWrite容器只能保证最终一致性,不能保证实时一致性,因此不适用于需要立即读取写入数据的场景。

相关推荐
Mos_x几秒前
计算机组成原理核心知识点梳理
java·后端
墨寒博客栈4 分钟前
Linux基础常用命令
java·linux·运维·服务器·前端
回忆是昨天里的海8 分钟前
k8s-部署springboot容器化应用
java·容器·kubernetes
INFINI Labs20 分钟前
使用 Docker Compose 轻松实现 INFINI Console 离线部署与持久化管理
java·docker·eureka·devops·docker compose·console·easyserach
Cosolar21 分钟前
国产麒麟系统 aarch64 架构 PostgreSQL 15 源码编译安装完整教程
java·后端
GalaxyPokemon28 分钟前
PlayerFeedback 插件开发日志
java·服务器·前端
天天摸鱼的java工程师1 小时前
别再写那些重复代码了!8年Java老兵教你用 Hutool 提升开发效率
java·后端
喝杯绿茶1 小时前
springboot中的事务
java·spring boot·后端
麦兜*1 小时前
多阶段构建:打造最小化的 Spring Boot Docker 镜像
java·spring boot·后端·spring cloud·docker
oak隔壁找我1 小时前
Spring Boot Starter 入门教程
java·后端