简介
代码语言:javascript复制java.util.concurrent;
CopyOnWriteArrayList
从 CopyOnWriteArrayList 的名字就能看出它是满足 CopyOnWrite 的 ArrayList,CopyOnWrite 的意思是说,当容器需要被修改的时候,不直接修改当前容器,而是先将当前容器进行 Copy,复制出一个新的容器,然后修改新的容器,完成修改之后,再将原容器的引用指向新的容器。这样就完成了整个修改过程。这样做的好处是,CopyOnWriteArrayList 利用了“不变性”原理,因为容器每次修改都是创建新副本,所以对于旧容器来说,其实是不可变的,也是线程安全的,无需进一步的同步操作。我们可以对 CopyOnWrite 容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素,也不会有修改。
CopyOnWriteArrayList 的所有修改操作(add,set等)都是通过创建底层数组的新副本来实现的,所以 CopyOnWrite 容器也是一种读写分离的思想体现,读和写使用不同的容器。和 ArrayList 不同的是,CopyOnWriteArrayList 的迭代器在迭代的时候,如果数组内容被修改了,CopyOnWriteArrayList 不会报 ConcurrentModificationException 的异常,因为迭代器使用的依然是旧数组,只不过迭代的内容可能已经过时了。
- 读操作可以尽可能的快,而写即使慢一些也没关系 在很多应用场景中,读操作可能会远远多于写操作。比如,有些系统级别的信息,往往只需要加载或者修改很少的次数,但是会被系统内所有模块频繁的访问。对于这种场景,我们最希望看到的就是读操作可以尽可能的快,而写即使慢一些也没关系。
- 读多写少 黑名单是最典型的场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单中,黑名单并不需要实时更新,可能每天晚上更新一次就可以了。当用户搜索时,会检查当前关键字在不在黑名单中,如果在,则提示不能搜索。这种读多写少的场景也很适合使用 CopyOnWrite 集合。
CopyOnWriteArrayList实现了List接口的所有方法,主要包含如下两个成员变量:
代码语言:javascript复制// 可重入锁,用于对写操作加锁
final transient ReentrantLock lock = new ReentrantLock();
// Object类型数组,存放数据,volatile修饰,目的是一个线程对这个字段的修改另外一个线程立即可见
private transient volatile Object[] array;
CopyOnWriteArraySet
CopyOnWriteArraySet
为线程安全的Set
实现,它是线程安全的无序的集合,可以将它理解成线程安全的HashSet。有意思的是,CopyOnWriteArraySet和HashSet虽然都继承于共同的父类AbstractSet;但是,HashSet是通过“散列表”实现的,而CopyOnWriteArraySet
则是通过“动态数组(CopyOnWriteArrayList
)”实现的,并不是散列表。
//CopyOnWriteArraySet内部采用CopyOnWriteArrayList存储元素
private final CopyOnWriteArrayList<E> al;
和HashSet
不一样的是,CopyOnWriteArraySet
内部采用CopyOnWriteArrayList
存储元素,这也是CopyOnWriteArraySet
名字的由来,因为CopyOnWriteArrayList
是线程安全的,CopyOnWriteArraySet
的方法都是基于CopyOnWriteArrayList
实现的,所以CopyOnWriteArraySet
自然而然也是线程安全的,同样的,在并发环境下获取数据是弱一致性的。
总结:CopyOnWriteArraySet就是去重的CopyOnWriteArrayList,在项目并发量比较大和读多写少的情况下,并且需要去除重复元素的list的话,可以使用CopyOnWriteArraySet。