菜单

Administrator
发布于 2026-05-15 / 1 阅读
0
0

Copy-On-Write

Copy-On-Write

Copy-On-Write(写时复制)是计算机领域相当经典的优化思想。如果问一个 Java 开发者 copy-on-write 有什么作用?他们往往第一反应就是:优雅地解决读多写少场景下的并发问题。

背景

众所周知,多线程环境下会出现 data race 的问题。以 Java 中的 ArrayList 为例,ArrayList 本身是不保证线程安全的。通常情况,要保证多线程环境下不出问题,就要给 ArrayList 加上读写锁:

  • 读要读锁
  • 写要写锁
  • 读与读之间不互斥
  • 读与写之间要互斥
  • 写与写之间也要互斥

然而,对于读多写少的场景来说,频繁地读取必然导致频繁地加锁,而与写互斥的情况却很少出现,这似乎有点不"经济"。

CopyOnWriteArrayList

这时候 CopyOnWriteArrayList(以下简称 COWList)就登场了。

COWList 的总体设计思想是:在读的过程中去掉了锁,而在写的过程中则需要引入互斥锁,但是这个锁不会影响到读本身,也就进一步释放了读的性能瓶颈。

核心源码分析

// 只截取了部分 CopyOnWriteArrayList 代码片段

/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;

/**
 * 读操作 —— 无锁
 */
public E get(int index) {
    return get(getArray(), index);
}

/**
 * 写操作 —— 互斥锁
 */
public boolean add(E e) {
    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();
    }
}

三个关键点

  1. COWList 读操作是无锁的
  2. COWList 写与写之间是互斥的
  3. 底层持有的数组变量 array 是通过 volatile 修饰的

JMM 的 happens-before 语义保证了 volatile 变量的内存可见性,这使得任意线程在任意时间点读取 array 变量的时候都能保证读到最新值。

而写的过程中,除了互斥保证以外,还需要将 array 数组拷贝一个副本出来,对副本进行修改后再将该副本的引用赋值给 array 变量,以替换原先的引用,而这个替换过程是原子性的。


评论