日常记录——多线程与高并发—并发容器Set实现类CopyOnWriteArraySet和ConcurrentSkipListSet

一、简介

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:有序,需要维护索引,弱一致性。

猜你喜欢

转载自blog.csdn.net/weixin_43001336/article/details/107240508