JUC学习之CopyOnWriteArraySet

一、简介

CopyOnWriteArraySet是线程安全的无序集合,可以将它理解成线程安全的HashSet。CopyOnWriteArraySet和HashSet都继承于共同的父类AbstractSet;但是,HashSet是通过“散列表(HashMap)”实现的,而CopyOnWriteArraySet则是通过上一节刚介绍的CopyOnWriteArrayList写时复制ArrayList实现的,并不是散列表。CopyOnWriteArraySet和CopyOnWriteArrayList区别就是CopyOnWriteArraySet中不允许有重复元素存在。

CopyOnWriteArraySet,也是基于“写时复制”的思想。事实上,CopyOnWriteArraySet内部引用了一个CopyOnWriteArrayList对象,以“组合”方式,委托CopyOnWriteArrayList对象实现了所有API功能。

声明如下:

public class CopyOnWriteArraySet<E>
extends AbstractSet<E>
implements Serializable

CopyOnWriteArraySet继承自AbstractSet,并且实现了序列化接口。

来看看JDK官网介绍:

CopyOnWriteArraySet为所有操作使用内部CopyOnWriteArrayList的集合。因此,它具有相同的基本特性:

  • 它最适合那些集大小通常很小、只读操作远远多于可变操作的应用程序,并且需要在遍历过程中防止线程之间的干扰;
  • 线程安全;
  • 可变操作(添加、设置、删除等)开销很大,因为它们通常需要复制整个底层数组;
  • 迭代器不支持变式删除操作;
  • 通过迭代器遍历非常快,不会遇到来自其他线程的干扰。迭代器依赖于构建迭代器时数组的不变快照;

二、常用API

【a】构造方法

CopyOnWriteArraySet()

创建一个空集

CopyOnWriteArraySet(Collection<? extends E> c)

创建一个包含指定集合的所有元素的集合。

【b】常用方法

方法返回值类型

方法描述

boolean

add(E e)

如果指定的元素尚未出现,则将其添加到此集合

boolean

addAll(Collection<? extends E> c)

将指定集合中的所有元素添加到此集合(如果它们尚未出现)

void

clear()

从这个集合中删除所有的元素

boolean

contains(Object o)

如果该集合包含指定的元素,则返回true

boolean

containsAll(Collection<?> c)

如果此集合包含指定集合的所有元素,则返回true

boolean

equals(Object o)

将指定的对象与此集合进行相等性比较

void

forEach(Consumer<? super E> action)

为可迭代的每个元素执行给定的操作,直到处理完所有元素或操作引发异常

boolean

isEmpty()

如果该集合不包含任何元素,则返回true

Iterator<E>

iterator()

按照添加元素的顺序,在这个集合中包含的元素上返回一个迭代器

boolean

remove(Object o)

如果指定的元素存在,则从该集合中移除它

boolean

removeAll(Collection<?> c)

从该集合中移除指定集合中包含的所有元素

boolean

retainAll(Collection<?> c)

仅保留此集合中包含在指定集合中的元素

int

size()

返回该集合中元素的数目。

Spliterator<E>

spliterator()

按照添加元素的顺序,在这个集合的元素上返回一个Spliterator

Object[]

toArray()

返回一个包含该集合中所有元素的数组

<T> T[]

toArray(T[] a)

返回一个包含该集合中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型

三、使用示例

下面通过一个简单的示例说明如何使用CopyOnWriteArraySet:

public class T06_CopyOnWriteArraySet {
    private static Set<String> set = new CopyOnWriteArraySet<>();

    static {
        for (int i = 0; i < 10; i++) {
            set.add(String.valueOf(i));
        }
    }

    public static void main(String[] args) {
        new Thread(() -> {
            for (int i = 100; i < 110; i++) {
                set.add(String.valueOf(i));
                try {
                    TimeUnit.MILLISECONDS.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " ");
        }
        System.out.println();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        iterator = set.iterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " ");
        }
    }
}

运行结果:

0 1 2 3 4 5 6 7 8 9 
0 1 2 3 4 5 6 7 8 9 100 101 

一个线程往集合中写数据,一个线程从集合中读取数据,可见,并发操作并不会发生ConcurrentModificationException并发修改异常.

四、源码阅读

CopyOnWriteArraySet是通过CopyOnWriteArrayList实现的,底层很多方法基本上全是调用的CopyOnWriteArrayList中的方法来实现,可以粗略讲一下,具体可以参考上一篇关于CopyOnWriteArrayList源码的详细介绍,两个结合起来应该不会特别难懂。

 重要属性说明: 

//序列化ID
private static final long serialVersionUID = 5457747651344034263L;

//通过聚合CopyOnWriteArrayList动态数组来完成所有功能
private final CopyOnWriteArrayList<E> al;

 构造方法:

/**
 * 创建一个空集合.
 */
public CopyOnWriteArraySet() {
    //实际上是创建了CopyOnWriteArrayList,即一个长度为0的对象数组
    al = new CopyOnWriteArrayList<E>();
}

/**
 * 创建一个包含指定集合的所有元素的集合.
 */
public CopyOnWriteArraySet(Collection<? extends E> c) {
    if (c.getClass() == CopyOnWriteArraySet.class) {
        @SuppressWarnings("unchecked") CopyOnWriteArraySet<E> cc =
            (CopyOnWriteArraySet<E>)c;
            //创建包含指定集合的集合
        al = new CopyOnWriteArrayList<E>(cc.al);
    }
    else {
        al = new CopyOnWriteArrayList<E>();
        //只有元素不存在,才添加进去
        al.addAllAbsent(c);
    }
}

常用方法说明:

【a】add(E e)、addAll(Collection<? extends E> c): 底层调用CopyOnWriteArrayList.addIfAbsent,只有元素不存在的时候,才会添加成功,否则添加失败,返回false.

/**
 * 如果指定的元素尚未出现,则将其添加到此集合。更正式地说,如果集合不包含元素e,则将指定的元素e添加到该集合中。如果该集合已经包含元素e,则调用将保持集合不变,并返回false.
 */
public boolean add(E e) {
    return al.addIfAbsent(e);
}

//同理
public boolean addAll(Collection<? extends E> c) {
    return al.addAllAbsent(c) > 0;
}

【b】remove(Object o) :删除集合中指定的元素

/**
 * 如果指定的元素存在,则从该集合中移除它
 */
public boolean remove(Object o) {
    //在CopyOnWriteArrayList,根据元素找出在数组中的下标,然后再进行删除
    return al.remove(o);
}

【c】size()、isEmpty()

/**
 * 返回该集合中元素的数目
 */
public int size() {
    return al.size();
}

/**
 * 如果这个集合不包含元素,则返回true
 */
public boolean isEmpty() {
    return al.isEmpty();
}

【d】迭代

/**
 * 返回的迭代器提供了构建迭代器时set状态的快照。遍历迭代器时不需要同步.
 */
public Iterator<E> iterator() {
    return al.iterator();
}

其他更加详细的方法可以参考上一篇文章CopyOnWriteArrayList.

五、总结

CopyOnWriteArraySet底层都是通过CopyOnWriteArrayList来实现的,同样也是基于“写时复制”的思想,那么它的特性也和CopyOnWriteArrayList是类似的,主要有以下几点:

  1. 适合“读多写少”且数据量不大的场景;
  2. 线程安全;
  3. 内存的使用较多;
  4. 迭代是对快照进行的,不会抛出ConcurrentModificationException,且迭代过程中不支持修改操作;
发布了222 篇原创文章 · 获赞 93 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/Weixiaohuai/article/details/104705514