一、简介
CopyOnWriteArraySet是线程安全的无序集合,可以将它理解成线程安全的HashSet。CopyOnWriteArraySet和HashSet都继承于共同的父类AbstractSet;但是,HashSet是通过“散列表(HashMap)”实现的,而CopyOnWriteArraySet则是通过上一节刚介绍的CopyOnWriteArrayList写时复制ArrayList实现的,并不是散列表。CopyOnWriteArraySet和CopyOnWriteArrayList区别就是CopyOnWriteArraySet中不允许有重复元素存在。
CopyOnWriteArraySet,也是基于“写时复制”的思想。事实上,CopyOnWriteArraySet内部引用了一个CopyOnWriteArrayList对象,以“组合”方式,委托CopyOnWriteArrayList对象实现了所有API功能。
声明如下:
public class CopyOnWriteArraySet<E>
extends AbstractSet<E>
implements Serializable
CopyOnWriteArraySet继承自AbstractSet,并且实现了序列化接口。
来看看JDK官网介绍:
CopyOnWriteArraySet为所有操作使用内部CopyOnWriteArrayList的集合。因此,它具有相同的基本特性:
- 它最适合那些集大小通常很小、只读操作远远多于可变操作的应用程序,并且需要在遍历过程中防止线程之间的干扰;
- 线程安全;
- 可变操作(添加、设置、删除等)开销很大,因为它们通常需要复制整个底层数组;
- 迭代器不支持变式删除操作;
- 通过迭代器遍历非常快,不会遇到来自其他线程的干扰。迭代器依赖于构建迭代器时数组的不变快照;
二、常用API
【a】构造方法
创建一个空集 |
CopyOnWriteArraySet(Collection<? extends E> c) 创建一个包含指定集合的所有元素的集合。 |
【b】常用方法
方法返回值类型 |
方法描述 |
boolean |
如果指定的元素尚未出现,则将其添加到此集合 |
boolean |
addAll(Collection<? extends E> c) 将指定集合中的所有元素添加到此集合(如果它们尚未出现) |
void |
clear() 从这个集合中删除所有的元素 |
boolean |
如果该集合包含指定的元素,则返回true |
boolean |
containsAll(Collection<?> c) 如果此集合包含指定集合的所有元素,则返回true |
boolean |
将指定的对象与此集合进行相等性比较 |
void |
forEach(Consumer<? super E> action) 为可迭代的每个元素执行给定的操作,直到处理完所有元素或操作引发异常 |
boolean |
isEmpty() 如果该集合不包含任何元素,则返回true |
iterator() 按照添加元素的顺序,在这个集合中包含的元素上返回一个迭代器 |
|
boolean |
如果指定的元素存在,则从该集合中移除它 |
boolean |
removeAll(Collection<?> c) 从该集合中移除指定集合中包含的所有元素 |
boolean |
retainAll(Collection<?> c) 仅保留此集合中包含在指定集合中的元素 |
int |
size() 返回该集合中元素的数目。 |
按照添加元素的顺序,在这个集合的元素上返回一个Spliterator |
|
Object[] |
toArray() 返回一个包含该集合中所有元素的数组 |
<T> T[] |
toArray(T[] a) 返回一个包含该集合中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型 |
三、使用示例
下面通过一个简单的示例说明如何使用CopyOnWriteArraySet:
public class T06_CopyOnWriteArraySet {
private static Set<String> set = new CopyOnWriteArraySet<>();
static {
for (int i = 0; i < 10; i++) {
set.add(String.valueOf(i));
}
}
public static void main(String[] args) {
new Thread(() -> {
for (int i = 100; i < 110; i++) {
set.add(String.valueOf(i));
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
System.out.println();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
iterator = set.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
}
}
运行结果:
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9 100 101
一个线程往集合中写数据,一个线程从集合中读取数据,可见,并发操作并不会发生ConcurrentModificationException并发修改异常.
四、源码阅读
CopyOnWriteArraySet是通过CopyOnWriteArrayList实现的,底层很多方法基本上全是调用的CopyOnWriteArrayList中的方法来实现,可以粗略讲一下,具体可以参考上一篇关于CopyOnWriteArrayList源码的详细介绍,两个结合起来应该不会特别难懂。
重要属性说明:
//序列化ID
private static final long serialVersionUID = 5457747651344034263L;
//通过聚合CopyOnWriteArrayList动态数组来完成所有功能
private final CopyOnWriteArrayList<E> al;
构造方法:
/**
* 创建一个空集合.
*/
public CopyOnWriteArraySet() {
//实际上是创建了CopyOnWriteArrayList,即一个长度为0的对象数组
al = new CopyOnWriteArrayList<E>();
}
/**
* 创建一个包含指定集合的所有元素的集合.
*/
public CopyOnWriteArraySet(Collection<? extends E> c) {
if (c.getClass() == CopyOnWriteArraySet.class) {
@SuppressWarnings("unchecked") CopyOnWriteArraySet<E> cc =
(CopyOnWriteArraySet<E>)c;
//创建包含指定集合的集合
al = new CopyOnWriteArrayList<E>(cc.al);
}
else {
al = new CopyOnWriteArrayList<E>();
//只有元素不存在,才添加进去
al.addAllAbsent(c);
}
}
常用方法说明:
【a】add(E e)、addAll(Collection<? extends E> c): 底层调用CopyOnWriteArrayList.addIfAbsent,只有元素不存在的时候,才会添加成功,否则添加失败,返回false.
/**
* 如果指定的元素尚未出现,则将其添加到此集合。更正式地说,如果集合不包含元素e,则将指定的元素e添加到该集合中。如果该集合已经包含元素e,则调用将保持集合不变,并返回false.
*/
public boolean add(E e) {
return al.addIfAbsent(e);
}
//同理
public boolean addAll(Collection<? extends E> c) {
return al.addAllAbsent(c) > 0;
}
【b】remove(Object o) :删除集合中指定的元素
/**
* 如果指定的元素存在,则从该集合中移除它
*/
public boolean remove(Object o) {
//在CopyOnWriteArrayList,根据元素找出在数组中的下标,然后再进行删除
return al.remove(o);
}
【c】size()、isEmpty()
/**
* 返回该集合中元素的数目
*/
public int size() {
return al.size();
}
/**
* 如果这个集合不包含元素,则返回true
*/
public boolean isEmpty() {
return al.isEmpty();
}
【d】迭代
/**
* 返回的迭代器提供了构建迭代器时set状态的快照。遍历迭代器时不需要同步.
*/
public Iterator<E> iterator() {
return al.iterator();
}
其他更加详细的方法可以参考上一篇文章CopyOnWriteArrayList.
五、总结
CopyOnWriteArraySet底层都是通过CopyOnWriteArrayList来实现的,同样也是基于“写时复制”的思想,那么它的特性也和CopyOnWriteArrayList是类似的,主要有以下几点:
- 适合“读多写少”且数据量不大的场景;
- 线程安全;
- 内存的使用较多;
- 迭代是对快照进行的,不会抛出ConcurrentModificationException,且迭代过程中不支持修改操作;