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容器只能保证最终一致性,不能保证实时一致性,因此不适用于需要立即读取写入数据的场景。

相关推荐
晴子呀6 分钟前
Spring底层原理大致脉络
java·后端·spring
只吹45°风13 分钟前
Java-ArrayList和LinkedList区别
java·arraylist·linkedlist·区别
阿华的代码王国20 分钟前
【JavaEE】多线程编程引入——认识Thread类
java·开发语言·数据结构·mysql·java-ee
黑蛋同志21 分钟前
array和linked list的区别
java
andrew_121927 分钟前
腾讯 IEG 游戏前沿技术 一面复盘
java·redis·sql·面试
寻求出路的程序媛34 分钟前
JVM —— 类加载器的分类,双亲委派机制
java·jvm·面试
这孩子叫逆36 分钟前
35. MyBatis中的缓存失效机制是如何工作的?
java·spring·mybatis
骆晨学长37 分钟前
基于SpringBoot的校园失物招领系统
java·spring boot
汇匠源37 分钟前
零工市场小程序:保障灵活就业
java·小程序·零工市场
计算机编程-吉哥40 分钟前
计算机毕业设计 二手图书交易系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试
java·spring boot·毕业设计·毕业论文·计算机毕业设计选题·计算机毕业设计开题报告·二手图书交易系统