JUC集合类 CopyOnWriteArraySet源码解析 JDK8

news/2024/5/20 9:27:32 标签: java, JUC, 多线程, 并发, 集合

文章目录

  • 前言
  • 与CopyOnWriteArrayList不同之处
    • addIfAbsent
    • addAllAbsent
  • 总结

前言

类似于上一篇讲的CopyOnWriteArrayList,CopyOnWriteArraySet可以认为是一个写时复制的HashSet。

CopyOnWriteArraySet的底层实现完全依赖了CopyOnWriteArrayList,它持有了一个CopyOnWriteArrayList类型的成员,很多方法的实现都是直接调用CopyOnWriteArrayList的同名方法。

这意味着CopyOnWriteArraySet的底层实现不再是散列表的实现,而只是一个普通数组,只不过现在CopyOnWriteArraySet表现地像一个HashSet而已,不过这对于使用者来说已经足够了。

JUC框架 系列文章目录

与CopyOnWriteArrayList不同之处

CopyOnWriteArraySet的底层实现完全依赖了CopyOnWriteArrayList是可以的,但问题是CopyOnWriteArrayList允许有重复元素,但CopyOnWriteArraySet作为一个HashSet却不能有重复元素。

为了解决这一问题,CopyOnWriteArrayList专门提供了addIfAbsentaddAllAbsent,以防止添加元素时会添加重复元素到里面去。

addIfAbsent

java">//CopyOnWriteArraySet
    public boolean add(E e) {
        return al.addIfAbsent(e);
    }
//CopyOnWriteArrayList
    public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();
        //indexOf返回的不是-1,说明元素是present而不是absent。即元素存在,所以直接返回false
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
        //如果元素不存在,则返回addIfAbsent的返回值
            addIfAbsent(e, snapshot);
    }

addIfAbsent,简单的说,就是只有这个元素不存在于set中时(adsent,缺席),才会加入该元素e,从而防止重复元素加入。

java">    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) {
            	//如果数组成员已经改变,需要考虑当前的数组成员会不会又拥有了参数e
                int common = Math.min(snapshot.length, len);
                //检查[0, snapshot.length)内的元素
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                //检查[snapshot.length, len)内的元素
                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();
        }
    }

addIfAbsent(E e, Object[] snapshot)方法需要考虑数组成员已经改变,当前的数组成员会不会又重新拥有了参数e。而if (snapshot != current)分支内的代码就是用来执行这额外的检查,而这个检查的手法和CopyOnWriteArrayList的remove(Object o)方法几乎一样,在此不赘述。重点在于,if (snapshot != current)分支的目的,是为了检查currrent中是否拥有参数e,如果有则需要退出。

但这个手法在这里有所简化,还是分为两种情况:

  1. snapshot.length >= len
  2. snapshot.length < len

如果snapshot.length >= len,for循环会检查到current的所有元素,并且此时common就是len,执行indexOf(e, current, common, len)时会直接返回-1。

如果snapshot.length < len,for循环只能检查到[0, snapshot.length)范围内的元素,剩下[snapshot.length, len)范围交给indexOf(e, current, common, len)检查。

如果if (snapshot != current)分支执行完毕都没有返回,则说明current中确实没有参数e

addAllAbsent

java">//CopyOnWriteArraySet
    public boolean addAll(Collection<? extends E> c) {
        return al.addAllAbsent(c) > 0;
    }
//CopyOnWriteArrayList
    public int addAllAbsent(Collection<? extends E> c) {
        Object[] cs = c.toArray();
        if (cs.length == 0)
            return 0;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            int added = 0;
            // uniquify and compact elements in cs
            for (int i = 0; i < cs.length; ++i) {
                Object e = cs[i];
                //先检查e是否在elements中
                if (indexOf(e, elements, 0, len) < 0 &&
                //再检查e是否在待添加元素set中
                    indexOf(e, cs, 0, added) < 0)
                    //cs被遍历过的范围都可以作为待添加set使用
                    cs[added++] = e;
            }
            if (added > 0) {//如果需要添加元素
                Object[] newElements = Arrays.copyOf(elements, len + added);
                System.arraycopy(cs, 0, newElements, len, added);
                setArray(newElements);
            }
            return added;
        } finally {
            lock.unlock();
        }
    }

The returned array will be “safe” in that no references to it are maintained by this collection. (In other words, this method must allocate a new array even if this collection is backed by an array). The caller is thus free to modify the returned array.

首先要知道Collection#toArray这个方法是保证了返回一个集合的数组副本(见上面注释),也就是说我们可以随意操作cs这个数组,而不用担心对参数c造成影响。
在这里插入图片描述
函数的主要逻辑如上图所示,indexOf(e, elements, 0, len)用来检查遍历元素e是否在elements中,indexOf(e, cs, 0, added)用来检查e是否在待添加元素set中。这样检查是为了cs中有重复的元素。

注意,实际上没有生成新的set作为待添加元素set,而是直接重复利用cs数组,因为每遍历过了当前元素后,这个元素的位置就可以随意占用了。

最后待添加元素set的范围就是cs[0, added-1],因为added是添加的元素个数。该函数返回的也是added。

总结

虽然CopyOnWriteArraySet的底层实现完全依赖了CopyOnWriteArrayList,但它还是保证了元素的唯一性。


http://www.niftyadmin.cn/n/768189.html

相关文章

JUC集合类 ConcurrentSkipListMap源码解析 JDK8

文章目录前言源码注释术语节点定义构造器get 查找操作返回情况findPredecessorput 插入操作假设新建层数没有超过最大层数假设新建层数超过了最大层数返回情况remove 删除操作findNodetryReduceLevel返回情况marker存在的必要性如果marker不存在marker存在时总结前言 Concurre…

ThreadLocalRandom#getProbe #advanceProbe浅析

前言 Briefly, a thread’s “probe” value is a non-zero hash code that (probably) does not collide with other existing threads with respect to any power of two collision space. When it does collide, it is pseudo-randomly adjusted (using a Marsaglia XorShif…

JUC集合类 ConcurrentHashMap源码解析 JDK8

文章目录前言常量成员节点类构造器put 插入操作加锁情况红黑树的binCount固定为2返回情况spreadinitTablehelpTransferresizeStampsizeCtl的低16bit退出循环的条件treeifyBintryPresizeaddCount计数部分计数部分结束时扩容部分CAS失败影响扩容fullAddCountwasUncontended的作用…

JDK8 ConcurrentHashMap的Bug集锦

前言 JDK8的ConcurrentHashMap并不是完美的&#xff0c;从https://bugs.openjdk.java.net/projects/JDK/issues上也可以看到JDK的很多Bug&#xff0c;当然&#xff0c;通过给concurrency-interest发邮件也可以和Doug Lea直接对话。 最重要的是&#xff0c;知道了这些bug的存在…

JUC集合类 ConcurrentLinkedQueue源码解析 JDK8

文章目录前言概述不变式基本不变式headtail初始化队列初始化Node初始化add/offer 入队操作出队操作pollpeekfirstremove 删除操作remove的bugsize 弱一致性的方法addAll迭代器总结前言 ConcurrentLinkedQueue是一种FIFO&#xff08;first-in-first-out 先入先出&#xff09;的…

JUC集合类 ConcurrentLinkedDeque源码解析 JDK8

文章目录前言概述linkFirst 入队pollFirst 获取并出队first()succ()unlink()unlinkFirstskipDeletedPredecessorsupdateHeadupdateTailgc-unlinking松弛阈值unlink的Unlink interior node逻辑peekFirst 仅获取remove 删除操作size迭代器总结前言 ConcurrentLinkedDeque是一个无…

JUC集合类 ArrayBlockingQueue源码解析 JDK8

文章目录前言成员构造器入队addofferput超时offer总结出队peekpolltake超时poll总结remove 删除操作总结前言 ArrayBlockingQueue是一种FIFO&#xff08;first-in-first-out 先入先出&#xff09;的有界阻塞队列&#xff0c;底层是数组&#xff0c;也支持从内部删除元素。并发…

JDK8 ArrayBlockingQueue迭代器 源码解析

文章目录前言Itr成员Itr构造器以及public方法Itrs对Itr的管理ArrayBlockingQueue里对Itrs的调用itrs.elementDequeued()itrs.removedAt(removeIndex)为什么Itrs会去掉失效的迭代器总结前言 ArrayBlockingQueue的迭代器也是弱一致性的&#xff0c;体现在于队列元素被删除后&…