Java 并发编程之 CopyOnWriteArrayList
Java juc About 5,127 words介绍
线程安全的,适合并发的集合类,对比ArrayList
。
读写分离
写操作是复制一份新的数组,读操作在原数组的快照中进行。
数据一致性
遍历时获取的是原数组的快照,对集合的add
、remove
不会引起并发修改异常,但遍历时无法保证数据是最新数据,只能保证数据的最终一致性。
优点
写操作的同时,允许读操作。
缺点
内存占用:每次写操作都会复制一份数组出来。
数据一致性:只能保存数据最终一致性。
代码示例
public class CopyOnWriteArrayListDemo {
private static CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
list.add(ThreadLocalRandom.current().nextInt(10000));
}).start();
}
new Thread(() -> {
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
System.out.println(next);
if (Integer.parseInt(next) % 2 == 0) {
set.remove(next);
}
}
}).start();
TimeUnit.SECONDS.sleep(1);
System.out.println(list.toString());
}
}
输出:
3527
7148
1178
7453
4962
9133
9904
5979
4204
9213
[3527, 7453, 9133, 5979, 9213]
源码解析
基于Java8
。
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// 悲观锁
final transient ReentrantLock lock = new ReentrantLock();
// 底层维护的数组
private transient volatile Object[] array;
// 无参构造
public CopyOnWriteArrayList() {
// 设置一个长度为 0 的 Object 数组
setArray(new Object[0]);
}
// 设置数组
final void setArray(Object[] a) {
array = a;
}
// 添加元素,上悲观锁
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();
}
}
// 获取元素
public E get(int index) {
return get(getArray(), index);
}
// 迭代器
public Iterator<E> iterator() {
// 构造一个迭代器,传入当前底层数组和游标第 0 位索引
return new COWIterator<E>(getArray(), 0);
}
static final class COWIterator<E> implements ListIterator<E> {
// CopyOnWriteArrayList 底层数组的快照
private final Object[] snapshot;
// 游标
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
public boolean hasNext() {
return cursor < snapshot.length;
}
// 获取的是快照数组的元素
@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
}
// CopyOnWriteArrayList toString() 方法也是返回当前底层数组,只能保持最终一致性
public String toString() {
return Arrays.toString(getArray());
}
}
与 Vector 对比
CopyOnWriteArrayList
效率更高,Vector
只能同时处理一种操作(先进入add()
方法,则必须等add()
方法执行完毕后才能执行其他方法,如:get()
)。
遍历时删除Vector
会抛出并发修改异常,CopyOnWriteArrayList
不会抛出异常,但是只能保证数据最终一致性,因为遍历的是原数组的快照。
Vector 代码示例
public class VectorDemo {
public static void main(String[] args) {
Vector<String> vector = new Vector<String>(){
@Override
public synchronized boolean add(String o) {
System.out.println(LocalDateTime.now() + " 添加数据开始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(LocalDateTime.now() + " 添加数据完成");
return super.add(o);
}
@Override
public synchronized String get(int index) {
System.out.println(LocalDateTime.now() + " 获取数据完成");
return super.get(index);
}
};
new Thread(() -> {
vector.add("1234");
}).start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(LocalDateTime.now() + " 获取数据开始");
System.out.println(vector.get(0));
}).start();
TimeUnit.SECONDS.sleep(5);
for (String s : vector) {
vector.remove("1234");
}
}
}
输出:
2021-04-02T20:46:20.856 添加数据开始
2021-04-02T20:46:21.805 获取数据开始
2021-04-02T20:46:23.857 添加数据完成
2021-04-02T20:46:23.857 获取数据完成
1234
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.Vector$Itr.checkForComodification(Vector.java:1184)
at java.util.Vector$Itr.next(Vector.java:1137)
at container.VectorDemo.main(VectorDemo.java:50)
Views: 2,025 · Posted: 2021-10-31
————        END        ————
Give me a Star, Thanks:)
https://github.com/fendoudebb/LiteNote扫描下方二维码关注公众号和小程序↓↓↓
Loading...