深入浅出CopyOnWriteArrayList

📅 发布时间:2026/7/12 2:47:14 👁️ 浏览次数:
深入浅出CopyOnWriteArrayList
CopyOnWriteArrayList 是线程安全的可以在多线程环境下使用。CopyOnWriteArrayList 遵循写时复制的原则每当对列表进行修改例如添加、删除或更改元素时都会创建列表的一个新副本这个新副本会替换旧的列表而对旧列表的所有读取操作仍然可以继续。由于在修改时创建了新的副本所以读取操作不需要锁定。这使得在多读取者和少写入者的情况下读取操作非常高效。当然由于每次写操作都会创建一个新的数组副本所以会增加存储和时间的开销。如果写操作非常频繁性能会受到影响。什么是 CopyOnWriteReentrantReadWriteLock 读写锁是通过读写分离的思想来实现的即读写锁将读写操作分别加锁从而实现读写操作的并发执行。但是读写锁也存在一些问题比如说在写锁执行后读线程会被阻塞直到写锁被释放后读线程才有机会获取到锁从而读到最新的数据站在读线程的角度来看读线程在任何时候都能获取到最新的数据满足数据实时性。而 CopyOnWriteArrayList 是通过 Copy-On-Write(COW)即写时复制的思想来通过延时更新的策略实现数据的最终一致性并且能够保证读线程间不阻塞。当然这要牺牲数据的实时性。通俗的讲CopyOnWrite 就是当我们往一个容器添加元素的时候不直接往容器中添加而是先复制出一个新的容器然后在新的容器里添加元素添加完之后再将原容器的引用指向新的容器。多个线程在读的时候不需要加锁因为当前容器不会添加任何元素。CopyOnWriteArrayList原理顾名思义实际上 CopyOnWriteArrayList 内部维护的就是一个数组/** The array, accessed only via getArray/setArray. */ private transient volatile Object[] array;该数组被 volatile 修饰能够保证数据的内存可见性。get 方法get 方法的源码如下public E get(int index) { return get(getArray(), index); } /** * Gets the array. Non-private so as to also be accessible * from CopyOnWriteArraySet class. */ final Object[] getArray() { return array; } private E get(Object[] a, int index) { return (E) a[index]; }get 方法的实现非常简单几乎就是一个“单线程”没有添加任何的线程安全控制没有加锁也没有 CAS 操作原因就是所有的读线程只会读取容器中的数据并不会进行修改。add 方法add 方法的源码如下public boolean add(E e) { final ReentrantLock lock this.lock; //1. 使用Lock,保证写线程在同一时刻只有一个 lock.lock(); try { //2. 获取旧数组引用 Object[] elements getArray(); int len elements.length; //3. 创建新的数组并将旧数组的数据复制到新数组中 Object[] newElements Arrays.copyOf(elements, len 1); //4. 往新数组中添加新的数据 newElements[len] e; //5. 将旧数组引用指向新的数组 setArray(newElements); return true; } finally { lock.unlock(); } }add 方法的逻辑也比较容易理解需要注意这么几点1、采用 ReentrantLock 保证同一时刻只有一个写线程正在进行数组的复制2、通过调用getArray()方法获取旧的数组。final Object[] getArray() { return array; }03、然后创建一个新的数组把旧的数组复制过来然后在新的数组中添加数据再将新的数组赋值给旧的数组引用。final void setArray(Object[] a) { array a; }根据 volatile 的 happens-before 规则所以这个更改对所有线程是立即可见的。04、最后在 finally 块中释放锁以便其他线程可以访问和修改列表。CopyOnWriteArrayList 的使用CopyOnWriteArrayList 的使用非常简单和 ArrayList 的使用几乎一样只是在创建对象的时候需要使用 CopyOnWriteArrayList 的构造方法如下所示CopyOnWriteArrayListString list new CopyOnWriteArrayList(); list.add(element1); list.add(element2); for (String element : list) { System.out.println(element); }CopyOnWriteArrayList 的缺点CopyOnWrite 容器有很多优点但是同时也存在两个问题即内存占用问题和数据一致性问题。所以在开发的时候需要特别注意。内存占用问题因为 CopyOnWrite 的写时复制机制在进行写操作的时候内存里会同时有两个对象旧的对象和新写入的对象。数据一致性问题CopyOnWrite 容器只能保证数据的最终一致性不能保证数据的实时一致性。所以如果你希望写入的的数据马上能读到请不要使用 CopyOnWrite 容器最好通过 ReentrantReadWriteLock 自定义一个的列表。比较一下 CopyOnWrite 和读写锁。相同点两者都是通过读写分离的思想来实现的读线程间是互不阻塞的不同点为了实现数据实时性在写锁被获取后读线程会阻塞或者当读锁被获取后写线程会阻塞从而解决“脏读”的问题。而 CopyOnWrite 对数据的更新是写时复制的因此读线程是延时感知的单不会存在阻塞的情况。