前面两篇讲了并发编程中线程安全HashMap:ConcurrentHashMap
,那么作为同样使用频率很高的List和Set,J.U.C当然也提供了相应的线程安全集合,就是Copy-On-Write
容器CopyOnWriteArrayList
和CopyOnWriteArraySet
。
这里的COW当然不是奶牛,而是Copy-On-Write
的简称,即写时复制,是一种用于程序设计中的优化策略。
COW的基本思路:
COW容器只有写操作与写操作之间是互斥的,读读和读写都不互斥。
优点:
缺点:
COW的设计思想的一些应用:内存管理(如Linux的fork()函数),数据存储(如redis),文件管理系统(如Linux的文件管理系统),软件开发(如Java的Copy-On-Write容器)。
理解了Copy-On-Write
思想,CopyOnWriteArrayList
和CopyOnWriteArraySet
的源码就很容易了。本文以CopyOnWriteArrayList
源码为例来分析Copy-On-Write
容器。
CopyOnWriteArrayList
只有两个属性,数组array用于存储数据,重入锁lock用于写操作的同步。
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
final transient ReentrantLock lock = new ReentrantLock();
private transient volatile Object[] array;
}
get()方法获取数据,真的不用注释和讲解,最简单的代码。唯一需要注意的一点就是get()方法是没有加锁的,不需要同步,读数据线程一定不会阻塞。
public E get(int index) {
return get(getArray(), index);
}
final Object[] getArray() {
return array;
}
private E get(Object[] a, int index) {
return (E) a[index];
}
代码很简单,基本过程就是按照COW思想的操作步骤:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();// 1. lock锁同步
try {
// 2. 旧数组复制出一个新数组
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 3. 新数组添加元素
newElements[len] = e;
// 4. 新数组引用赋给array
setArray(newElements);
return true;
} finally {
lock.unlock();// 解锁
}
}
Copy-On-Write并发容器用于读多写少的并发场,如商品的访问和更新,排行榜,白名单/黑名单等。
举例:一个充值排行榜的功能,排行榜会有很多人查看访问,但是只有充值之后才会修改排行榜上的数据,或者充值之后也不更新,只有每天晚上9点更新排行榜,标准的读多写少。
public class CopyOnWriteArrayListTest {
public static CopyOnWriteArrayList<Integer> rankIds = new CopyOnWriteArrayList<Integer>();
public static void addRankIds(int id) {
/*
* 获取id在rankIds中的排序,代码省略
* 假设id应该在排行榜中的第一个
*/
rankIds.add(0, id);
}
}
Copy-On-Write并发容器处理并发问题的原理:
源码的并不只在于学习编程方法,更重要的是理解源码的设计思想,能够在开发和设计中运用。