关键词:CopyOnWriteArrayList CopyOnWriteArraySet
在《JUC之CopyOnWriteArrayList》中我们学习了CopyOnWriteArrayList,现在我们再来看看CopyOnWriteArraySet。CopyOnWriteArraySet实现了Set接口,与List不同的是,添加到List中的元素是允许重复的,而添加到Set中的数据是不允许重复的,是唯一存在的。
CopyOnWriteArraySet并没有完全从零开始去实现不重复元素集合的功能,而是依赖了CopyOnWriteArrayList。利用CopyOnWriteArrayList中提供的一些方法来实现其自身的功能。
字段:
private final CopyOnWriteArrayList<E> al;
在CopyOnWriteArraySet中持有一个CopyOnWriteArrayList对象,通过使用CopyOnWriteArrayList中相应的方法来实现其自身的方法。一个CopyOnWriteArraySet对象持有一个CopyOnWriteArrayList对象。
构造器:
在构造器中对CopyOnWriteArrayList进行初始化。
public CopyOnWriteArraySet() {
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);
}
}
方法:
因为CopyOnWriteArraySet的实现借助了CopyOnWriteArrayList。因此CopyOnWriteArraySet的很多方法实现就是在内部调用了CopyOnWriteArrayList对应的方法,将功能实现转发给CopyOnWriteArrayList。比如:
- size
public int size() {
return al.size();
}
- isEmpty
public boolean isEmpty() {
return al.isEmpty();
}
- contains
public boolean contains(Object o) {
return al.contains(o);
}
等等,关于其他的方法感兴趣的同学可自行查看,实现方式都是一样的。
下面来看看CopyOnWriteArraySet中add方法的时候,其使用了CopyOnWriteArrayList中的一个方法,该方法在《JUC之CopyOnWriteArrayList》中并没有介绍。
- add
public boolean add(E e) {
return al.addIfAbsent(e);
}
调用了CopyOnWriteArrayList的addIfAbsent方法。从方法名称看,当不存在的时候则添加,即当添加的元素e在集合中不存在的时候才将该元素添加到集合中,否则不添加。根据这样的规则的话就可以实现集合中元素不会出现重复数据。
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
indexOf方法我们在《JUC之CopyOnWriteArrayList》中已经介绍了,其是去获取元素e在集合中的下标,当集合中存在元素e时,则返回对应的下标值,如果集合中不存在元素e时,则返回-1。所以如果indexOf的返回值>=0的话,表示元素e存在于集合中,此时就不需要将元素添加到集合中了,addIfAbsent方法直接返回false。
private static int indexOf(Object o, Object[] elements,
int index, int fence) {
if (o == null) {
for (int i = index; i < fence; i++)
if (elements[i] == null)
return i;
} else {
for (int i = index; i < fence; i++)
if (o.equals(elements[i]))
return i;
}
return -1;
}
addIfAbsent(e, snapshot):
执行将元素添加到集合中。该方法的时候,依然延续模式,使用ReentrantLock加锁来保证线程安全,并对数组内容进行拷贝。
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
①if (snapshot != current)
snapshot是通过getArray()获取的,而current也是通过getArray()获取的,咋两个数组会有可能不是同一个对象吗?答案是存在这种可能,这种可能就是在snapshot通过getArray获取到数组后,代码还没有执行到current = getArray()时,有其他线程对集合进行了写操作,此时获取到的array对象就不会是同一个了。(关于为什么有了写操作就不是同一个数组对象了,是因为在执行写操作时,会拷贝出一个新的数组对象,然后将新的数组对象赋值给array)