一、简介
1.CopyOnWriteArraySet:一个基于ReentrantLock锁的线程安全的set接口实现类。内部实际使用CopyOnWriteArrayList,底层同样是数组结构。
add方法:在add前先判断是否已经存在,存在返回fasle,否则复制扩展长度,然后添加到数据内。
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
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) {
// 针对与另一个addXXX操作的竞争而优化
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();
}
}
2.ConcurrentSkipListSet:底层是链表结构,基于ConcurrentSkipListMap的实现,将map中所有的value设置为true,并发通过cas操作支持,有序。
结构示例如下:例如查找哈希值为3的节点,先确定在头结点和哈希值为5这个节点内,然后在确定在2-5内,然后定位到3。
add方法:直接调用ConcurrentSkipListMap方法,value传为true。
public boolean add(E e) {
return m.putIfAbsent(e, Boolean.TRUE) == null;
}
public V put(K key, V value) {
if (value == null)
throw new NullPointerException();
return doPut(key, value, false);
}
private V doPut(K kkey, V value, boolean onlyIfAbsent) {
Comparable<? super K> key = comparable(kkey);
for (;;) {
Node<K,V> b = findPredecessor(key);//查找前驱数据节点
Node<K,V> n = b.next;//后继节点
for (;;) {
if (n != null) {
Node<K,V> f = n.next;
//后继两次读取不一致,重试
if (n != b.next) // inconsistent read
break;
Object v = n.value;
//后继数据节点的值为null,表示该数据节点标记为已删除,删除该数据节点并重试。
if (v == null) { // n is deleted
n.helpDelete(b, f);
break;
}
//b节点被标记为已删除,重试
if (v == n || b.value == null) // b is deleted
break;
int c = key.compareTo(n.key);
if (c > 0) {//给定key值大于当前,后移,继续寻找合适的插入点
b = n;
n = f;
continue;
}
if (c == 0) {//找到 onlyIfAbsent 入参就位true 设置值成功
if (onlyIfAbsent || n.casValue(v, value))
return (V)v;
else
break; // restart if lost race to replace value
}
}
//没有找到,新建数据节点
Node<K,V> z = new Node<K,V>(kkey, value, n);
//绑定到下一节点
if (!b.casNext(n, z))
break; // restart if lost race to append to b
int level = randomLevel();//随机的索引级别
if (level > 0)
//建立索引
insertIndex(z, level);
return null;
}
}
}
private void insertIndex(Node<K,V> z, int level) {
HeadIndex<K,V> h = head;
int max = h.level;
if (level <= max) {//索引级别已经存在,在当前索引级别以及底层索引级别上都添加该节点的索引
Index<K,V> idx = null;
for (int i = 1; i <= level; ++i)//首先得到一个包含1~level个索引级别的down关系的链表,最后的idx为最高level索引
idx = new Index<K,V>(z, idx, null);
addIndex(idx, h, level);//新增索引
} else { // 新增索引级别
//索引级别加一
level = max + 1;
Index<K,V>[] idxs = (Index<K,V>[])new Index[level+1];
Index<K,V> idx = null;
//更新索引
for (int i = 1; i <= level; ++i)
idxs[i] = idx = new Index<K,V>(z, idx, null);
HeadIndex<K,V> oldh;
int k;
for (;;) {
oldh = head;
int oldLevel = oldh.level;
//更新头索引级别
if (level <= oldLevel) { // lost race to add level
k = level;
break;
}
HeadIndex<K,V> newh = oldh;
Node<K,V> oldbase = oldh.node;
//更新head索引
for (int j = oldLevel+1; j <= level; ++j)
newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
if (casHead(oldh, newh)) {
k = oldLevel;
break;
}
}
addIndex(idxs[k], oldh, k);
}
}
二、代码代码举例
说明:创建两个线程分别向CopyOnWriteArraySet、ConcurrentSkipListSet、HashSet容器对象添加10000个元素,最后打印容器长度。CopyOnWriteArraySet结果:20000,ConcurrentSkipListSet结果:20000,HashSet:<20000(添加元素方法不是同步的)。
public static void main(String[] args) {
//Set<UUID> set = new HashSet<>();
//CopyOnWriteArraySet<UUID> set = new CopyOnWriteArraySet<>();
ConcurrentSkipListSet<UUID> set = new ConcurrentSkipListSet<>();
new Thread(() ->{
for (int i = 0; i < 10000; i++) {
set.add(UUID.randomUUID());
}
}).start();
new Thread(() ->{
for (int i = 0; i < 10000; i++) {
set.add(UUID.randomUUID());
}
}).start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(set.size());
}
三、CopyOnWriteArraySet和ConcurrentSkipListSet比较
1.CopyOnWriteArraySet:无序,添加时内存占有率高,弱一致性。
2.ConcurrentSkipListSet:有序,需要维护索引,弱一致性。