2023-09-10  阅读(32)
原文作者:keep_trying 原文地址: https://blog.csdn.net/yjp198713/article/details/79000833

一、CopyOnWriteArraySet介绍

它是线程安全的无序的集合,可以将它理解成线程安全的HashSet。有意思的是,CopyOnWriteArraySet和HashSet虽然都继承于共同的父类AbstractSet;但是,HashSet是通过“散列表(HashMap)”实现的,而CopyOnWriteArraySet则是通过“动态数组(CopyOnWriteArrayList)”实现的,并不是散列表。
和CopyOnWriteArrayList类似,CopyOnWriteArraySet具有以下特性:

  • 它最适合于具有以下特征的应用程序:Set 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。
  • 它是线程安全的。
  • 因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大。
  • 迭代器支持hasNext(), next()等不可变操作,但不支持可变 remove()等 操作。
  • 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。

二、CopyOnWriteArraySet原理和数据结构

CopyOnWriteArraySet的数据结构,如下图所示:

202309102210027171.png

说明:

  1. CopyOnWriteArraySet继承于AbstractSet,这就意味着它是一个集合。
  2. CopyOnWriteArraySet包含CopyOnWriteArrayList对象,它是通过CopyOnWriteArrayList实现的。而CopyOnWriteArrayList本质是个动态数组队列,所以CopyOnWriteArraySet相当于通过通过动态数组实现的“集合”! CopyOnWriteArrayList中允许有重复的元素;但是,CopyOnWriteArraySet是一个集合,所以它不能有重复集合。因此,CopyOnWriteArrayList额外提供了addIfAbsent()和addAllAbsent()这两个添加元素的API,通过这些API来添加元素时,只有当元素不存在时才执行添加操作!
    至于CopyOnWriteArraySet的“线程安全”机制,和CopyOnWriteArrayList一样,是通过volatile和互斥锁来实现的。

三、CopyOnWriteArraySet函数列表

    // 创建一个空 set。
    CopyOnWriteArraySet()
    // 创建一个包含指定 collection 所有元素的 set。
    CopyOnWriteArraySet(Collection<? extends E> c)
    
    // 如果指定元素并不存在于此 set 中,则添加它。
    boolean add(E e)
    // 如果此 set 中没有指定 collection 中的所有元素,则将它们都添加到此 set 中。
    boolean addAll(Collection<? extends E> c)
    // 移除此 set 中的所有元素。
    void clear()
    // 如果此 set 包含指定元素,则返回 true。
    boolean contains(Object o)
    // 如果此 set 包含指定 collection 的所有元素,则返回 true。
    boolean containsAll(Collection<?> c)
    // 比较指定对象与此 set 的相等性。
    boolean equals(Object o)
    // 如果此 set 不包含任何元素,则返回 true。
    boolean isEmpty()
    // 返回按照元素添加顺序在此 set 中包含的元素上进行迭代的迭代器。
    Iterator<E> iterator()
    // 如果指定元素存在于此 set 中,则将其移除。
    boolean remove(Object o)
    // 移除此 set 中包含在指定 collection 中的所有元素。
    boolean removeAll(Collection<?> c)
    // 仅保留此 set 中那些包含在指定 collection 中的元素。
    boolean retainAll(Collection<?> c)
    // 返回此 set 中的元素数目。
    int size()
    // 返回一个包含此 set 所有元素的数组。
    Object[] toArray()
    // 返回一个包含此 set 所有元素的数组;返回数组的运行时类型是指定数组的类型。
    <T> T[] toArray(T[] a)

四、CopyOnWriteArraySet源码

CopyOnWriteArraySet.java的完整源码如下:

  1. 添加
    以add(E e)为例,来对“CopyOnWriteArraySet的添加操作”进行说明。下面是add(E e)的代码:
     public boolean add(E e) {
            return al.addIfAbsent(e);
     }

al 是CopyOnWriteArraySet的一个成员变量如下

    private final CopyOnWriteArrayList<E> al;

CopyOnWriteArraySet的add操作,实际上是调用CopyOnWriteArrayList的addIfAbsent()方法

    // 添加元素(如果不存在)。
    public boolean addIfAbsent(E e) {
    //获取数组元素
            Object[] snapshot = getArray(); 
    //判断元素在不在数组里,如果在的话返回false,否则调用addIfAbsent()方法
            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;
    //判断两个数组是否相等(多线程访问时可能会改变数组) 
    //线程在获取锁之前,可能对current 做了操作(增加、修改、删除)
    //current 是最新的数组,snapshot是旧的add()操作之前的数组
                if (snapshot != current) {
                    // Optimize for lost race to another addXXX operation
                    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();
            }
        }

CopyOnWriteArraySet是通过CopyOnWriteArrayList实现的,它的API基本上都是通过调用CopyOnWriteArrayList的API来实现的。相信对CopyOnWriteArrayList了解的话,对CopyOnWriteArraySet的了解是水到渠成的事;所以,这里就不再对CopyOnWriteArraySet的代码进行详细的解析了。


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文