JUC集合类 CopyOnWriteArraySet源码解析 JDK8

文章目录

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

前言

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

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

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

JUC框架 系列文章目录

与CopyOnWriteArrayList不同之处

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

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

addIfAbsent

//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,从而防止重复元素加入。

    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

//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造成影响。
JUC集合类 CopyOnWriteArraySet源码解析 JDK8_第1张图片
函数的主要逻辑如上图所示,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,但它还是保证了元素的唯一性。

你可能感兴趣的:(Java)