目录
一、使用示例
CopyOnWriteList
:是 JDK 在并发包(java.util.concurrent)下的一个类,它是 ArrayList 的一个线程安全的变体。CopyOnWrite 即读写分离,读时共享,写时复制, 通俗的理解是:当我们往一个 List 添加元素的时候,不直接往当前 List 添加,而是先将当前 List 进行 Copy,复制出一个新的 List,然后新的容器里添加元素,添加完元素之后,再将原 List 的引用指向新的 List。这种设计使得读操作可以在原数组上进行,而不需要加锁,从而大大提高了读操作的并发性。
下面是一个经典的 10 线程并发场景下,CopyOnWriteArrayList 的线程安全使用实例:
import java.util.concurrent.*;
/**
* <p> @Title DemoController
* <p> @Description 测试Controller
*
* @author ACGkaka
* @date 2023/4/24 18:02
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
// 创建线程池
ExecutorService pool = new ThreadPoolExecutor(
10,
10,
1,
TimeUnit.MINUTES,
new LinkedBlockingQueue<>(10000),
r -> new Thread(r, "MyThread-" + r.hashCode()),
new ThreadPoolExecutor.AbortPolicy()
);
long start = System.currentTimeMillis();
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
int num = i;
pool.execute(() -> {
try {
Thread.sleep(100L);
list.add(num);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + " ms");
System.out.println(list);
pool.shutdown();
}
}
二、源码解析
2.1 成员变量
CopyOnWriteArrayList 类的有两个成员变量:
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8673264195747942595L;
/**
* The lock protecting all mutators
* ------------------------------
* 保护所有数组变更的锁。
*/
final transient ReentrantLock lock = new ReentrantLock();
/**
* The array, accessed only via getArray/setArray.
* ------------------------------
* 该数组只能通过getArray/setArray进行访问。
*/
private transient volatile Object[] array;
}
- lock: 用于在每次数组变更的时候加锁。
- array: 存储对象的数组。
2.2 new CopyOnWriteList<>() 解析
/**
* Creates an empty list.
* ------------------------------
* 创建一个空的集合
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
/**
* Sets the array.
* ------------------------------
* 设置数组
*/
final void setArray(Object[] a) {
array = a;
}
可以看到在 new CopyOnWriteList 实例的时候,初始化了一个长度为0的空数组。
2.3 add() 解析
源码如下:
/**
* Appends the specified element to the end of this list.
* ------------------------------
* 将指定的元素追加到此列表的末尾。
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
// 成员变量中声明的全局锁
final ReentrantLock lock = this.lock;
// 对当前实例进行加锁
lock.lock();
try {
// 获取数组
Object[] elements = getArray();
// 获取数组长度
int len = elements.length;
// 复制数组,长度+1
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 将数组最后一个元素赋值为e
newElements[len] = e;
// 更新数组
setArray(newElements);
return true;
} finally {
// 释放锁
lock.unlock();
}
}
/**
* Gets the array. Non-private so as to also be accessible
* from CopyOnWriteArraySet class.
* ------------------------------
* 获取数组。非私有化,因此也可以从 CopyOnWriteArraySet 类访问。
*/
final Object[] getArray() {
return array;
}
由源码可知,add() 操作是:通过使用实例内的锁,在往数组中添加元素的时候进行加锁操作,保证同一实例同一时间只有一处变更在进行。
2.4 get() 解析
源码如下:
/**
* 获取元素
*/
public E get(int index) {
return get(getArray(), index);
}
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
由源码可知,get() 操作是:直接从数组中获取元素,不进行加锁操作。
2.5 remove() 解析
源码如下:
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices). Returns the element that was removed from the list.
* ------------------------------
* 从此列表中的指定位置移除元素。将任何后续元素向左移动(从其索引中减去一)。
* 返回从列表中移除的元素。
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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)
// 没有需要左移的元素,直接更新数组
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();
}
}
由源码可知,remove() 操作是:先获取指定元素,然后全局加锁,再分别将指定位置左侧和右侧的元素复制到新数组中M。
2.6 size() 解析
/**
* Returns the number of elements in this list.
* ------------------------------
* 返回列表中元素的数量。
*
* @return the number of elements in this list
*/
public int size() {
return getArray().length;
}
由源码可知,size() 操作是:直接获取数组的长度,不进行加锁。
三、总结
综上所述,当我们往一个 List 添加元素的时候,不直接往当前 List 添加,而是先 全局加锁,将当前 List 进行 Copy,复制出一个新的 List,然后新的容器里添加元素,添加完元素之后,再将原 List 的引用指向新的 List,删除等其他操作亦是如此。
整理完毕,完结撒花~ 🌻