一、简介
CopyOnWriteArraySet是线程安全的Set集合,相当于线程安全的HashSet。
注意:HashSet的实现是通过散列表HashMap实现的,但是CopyOnWriteArraySet是通过动态数组
CopyOnWriteArrayList实现的
二、特性
- 适用于数据量较小且读多写少的场景
- 它是线程安全的
- 新增和删除等修改数据的操作开销很大,涉及到数组复制后续源码解析会详解
- 迭代器只支持读取不支持变更,并且在迭代过程中其他线程写入集合不会发生并发冲突
- 迭代器的数据来源于迭代时的快照数据
三、数据结构
CopyOnWriteArraySet的底层数据结构是CopyOnWriteArrayList实现的线程安全的可变数组
public class CopyOnWriteArraySet<E> extends AbstractSet<E>implements java.io.Serializable {
private static final long serialVersionUID = 5457747651344034263L;
private final CopyOnWriteArrayList<E> al;
}
四、部分源码解析
1. 构造方法
- 无参构造方法,构造一个空的CopyOnWriteArrayList
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
- 通过Collection构造
public CopyOnWriteArraySet(Collection<? extends E> c) {
// 如果是CopyOnWriteArraySet类型,则获取到底层的CopyOnWriteArrayList对象创建新的CopyOnWriteArrayList对象
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);
}
}
2. 变更数据方法
- add方法,直接调用CopyOnWriteArrayList的addIfAbsent方法实现不重复的插入数据
public boolean add(E e) {
return al.addIfAbsent(e);
}
- remove方法,直接调用CopyOnWriteArrayList的remove方法实现删除数据
public boolean remove(Object o) {
return al.remove(o);
}
CopyOnWriteArraySet变更数据总结:
CopyOnWriteArraySet完全依赖于内部的CopyOnWriteArrayList的方法来实现,逻辑也很简单,
具体逻辑可以参考上一篇的CopyOnWriteArrayList的文章
3. 迭代器方法
- iterator方法,同样也是直接调用CopyOnWriteArrayList的迭代器方法
public Iterator<E> iterator() {
return al.iterator();
}
四、案例
案例一:对比CopyOnWriteArraySet和HashSet
- 公共类
private static void printAll(Set<String> set) {
System.out.println(set);
}
static class SetRunnable implements Runnable {
private final Set<String> set;
SetRunnable(Set<String> set) {
this.set = set;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
set.add(Thread.currentThread().getName() + "-" + i);
printAll(set);
}
}
}
- 多线程操作HashSet
@Test
public void notUseJuc() throws InterruptedException {
Set<String> set = new HashSet<>();
final Thread th1 = new Thread(new SetRunnable(set));
final Thread th2 = new Thread(new SetRunnable(set));
th1.start();
th2.start();
th2.join();
}
执行过程中有概率报错:
D:\mysoft\jdk-8u271\bin\java.exe -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\mysoft\JetBrains\IntelliJ IDEA 2022.2.3\lib\idea_rt.jar=56700:D:\mysoft\JetBrains\IntelliJ IDEA 2022.2.3\bin" -Dfile.encoding=UTF-8 -classpath "D:\mysoft\JetBrains\IntelliJ IDEA 2022.2.3\lib\idea_rt.jar;D:\mysoft\JetBrains\IntelliJ IDEA 2022.2.3\plugins\junit\lib\junit5-rt.jar;D:\mysoft\JetBrains\IntelliJ IDEA 2022.2.3\plugins\junit\lib\junit-rt.jar;D:\mysoft\jdk-8u271\jre\lib\charsets.jar;D:\mysoft\jdk-8u271\jre\lib\deploy.jar;D:\mysoft\jdk-8u271\jre\lib\ext\access-bridge-64.jar;D:\mysoft\jdk-8u271\jre\lib\ext\cldrdata.jar;D:\mysoft\jdk-8u271\jre\lib\ext\dnsns.jar;D:\mysoft\jdk-8u271\jre\lib\ext\jaccess.jar;D:\mysoft\jdk-8u271\jre\lib\ext\jfxrt.jar;D:\mysoft\jdk-8u271\jre\lib\ext\localedata.jar;D:\mysoft\jdk-8u271\jre\lib\ext\nashorn.jar;D:\mysoft\jdk-8u271\jre\lib\ext\sunec.jar;D:\mysoft\jdk-8u271\jre\lib\ext\sunjce_provider.jar;D:\mysoft\jdk-8u271\jre\lib\ext\sunmscapi.jar;D:\mysoft\jdk-8u271\jre\lib\ext\sunpkcs11.jar;D:\mysoft\jdk-8u271\jre\lib\ext\zipfs.jar;D:\mysoft\jdk-8u271\jre\lib\javaws.jar;D:\mysoft\jdk-8u271\jre\lib\jce.jar;D:\mysoft\jdk-8u271\jre\lib\jfr.jar;D:\mysoft\jdk-8u271\jre\lib\jfxswt.jar;D:\mysoft\jdk-8u271\jre\lib\jsse.jar;D:\mysoft\jdk-8u271\jre\lib\management-agent.jar;D:\mysoft\jdk-8u271\jre\lib\plugin.jar;D:\mysoft\jdk-8u271\jre\lib\resources.jar;D:\mysoft\jdk-8u271\jre\lib\rt.jar;D:\workspace\myself\mycsdn-demo\target\test-classes;D:\mavenRepository\junit\junit\4.13.2\junit-4.13.2.jar;D:\mavenRepository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 com.bufan.juc.JucSetTest,notUseJuc
[Thread-0-0, Thread-1-0]
[Thread-1-1, Thread-0-0, Thread-1-0]
[Thread-1-1, Thread-1-2, Thread-0-0, Thread-1-0]
[Thread-1-3, Thread-1-1, Thread-1-2, Thread-0-0, Thread-1-0]
[Thread-1-3, Thread-1-4, Thread-1-1, Thread-1-2, Thread-0-0, Thread-1-0]
[Thread-1-5, Thread-1-3, Thread-1-4, Thread-1-1, Thread-1-2, Thread-0-0, Thread-1-0]
[Thread-1-5, Thread-1-6, Thread-1-3, Thread-1-4, Thread-1-1, Thread-1-2, Thread-0-0, Thread-1-0]
[Thread-1-7, Thread-1-5, Thread-1-6, Thread-1-3, Thread-1-4, Thread-1-1, Thread-1-2, Thread-0-0, Thread-1-0]
[Thread-1-7, Thread-1-8, Thread-1-5, Thread-1-6, Thread-1-3, Thread-1-4, Thread-1-1, Thread-1-2, Thread-0-0, Thread-1-0]
[Thread-1-9, Thread-1-7, Thread-1-8, Thread-1-5, Thread-1-6, Thread-1-3, Thread-1-4, Thread-1-1, Thread-1-2, Thread-0-0, Thread-1-0]
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
at java.util.HashMap$KeyIterator.next(HashMap.java:1469)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.bufan.juc.JucSetTest.printAll(JucSetTest.java:40)
at com.bufan.juc.JucSetTest.access$000(JucSetTest.java:15)
at com.bufan.juc.JucSetTest$SetRunnable.run(JucSetTest.java:55)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
- 多线程操作CopyOnWriteArraySet
@Test
public void useJuc() throws InterruptedException {
Set<String> list = new CopyOnWriteArraySet<>();
final Thread th1 = new Thread(new SetRunnable(list));
final Thread th2 = new Thread(new SetRunnable(list));
th1.start();
th2.start();
th2.join();
}
执行过程不会报并发异常
D:\mysoft\jdk-8u271\bin\java.exe -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\mysoft\JetBrains\IntelliJ IDEA 2022.2.3\lib\idea_rt.jar=56769:D:\mysoft\JetBrains\IntelliJ IDEA 2022.2.3\bin" -Dfile.encoding=UTF-8 -classpath "D:\mysoft\JetBrains\IntelliJ IDEA 2022.2.3\lib\idea_rt.jar;D:\mysoft\JetBrains\IntelliJ IDEA 2022.2.3\plugins\junit\lib\junit5-rt.jar;D:\mysoft\JetBrains\IntelliJ IDEA 2022.2.3\plugins\junit\lib\junit-rt.jar;D:\mysoft\jdk-8u271\jre\lib\charsets.jar;D:\mysoft\jdk-8u271\jre\lib\deploy.jar;D:\mysoft\jdk-8u271\jre\lib\ext\access-bridge-64.jar;D:\mysoft\jdk-8u271\jre\lib\ext\cldrdata.jar;D:\mysoft\jdk-8u271\jre\lib\ext\dnsns.jar;D:\mysoft\jdk-8u271\jre\lib\ext\jaccess.jar;D:\mysoft\jdk-8u271\jre\lib\ext\jfxrt.jar;D:\mysoft\jdk-8u271\jre\lib\ext\localedata.jar;D:\mysoft\jdk-8u271\jre\lib\ext\nashorn.jar;D:\mysoft\jdk-8u271\jre\lib\ext\sunec.jar;D:\mysoft\jdk-8u271\jre\lib\ext\sunjce_provider.jar;D:\mysoft\jdk-8u271\jre\lib\ext\sunmscapi.jar;D:\mysoft\jdk-8u271\jre\lib\ext\sunpkcs11.jar;D:\mysoft\jdk-8u271\jre\lib\ext\zipfs.jar;D:\mysoft\jdk-8u271\jre\lib\javaws.jar;D:\mysoft\jdk-8u271\jre\lib\jce.jar;D:\mysoft\jdk-8u271\jre\lib\jfr.jar;D:\mysoft\jdk-8u271\jre\lib\jfxswt.jar;D:\mysoft\jdk-8u271\jre\lib\jsse.jar;D:\mysoft\jdk-8u271\jre\lib\management-agent.jar;D:\mysoft\jdk-8u271\jre\lib\plugin.jar;D:\mysoft\jdk-8u271\jre\lib\resources.jar;D:\mysoft\jdk-8u271\jre\lib\rt.jar;D:\workspace\myself\mycsdn-demo\target\test-classes;D:\mavenRepository\junit\junit\4.13.2\junit-4.13.2.jar;D:\mavenRepository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 com.bufan.juc.JucSetTest,useJuc
[Thread-0-0, Thread-1-0]
[Thread-0-0, Thread-1-0]
[Thread-0-0, Thread-1-0, Thread-0-1]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1, Thread-0-2]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1, Thread-0-2, Thread-1-2]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1, Thread-0-2, Thread-1-2, Thread-0-3]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1, Thread-0-2, Thread-1-2, Thread-0-3, Thread-1-3]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1, Thread-0-2, Thread-1-2, Thread-0-3, Thread-1-3, Thread-0-4]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1, Thread-0-2, Thread-1-2, Thread-0-3, Thread-1-3, Thread-0-4, Thread-1-4]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1, Thread-0-2, Thread-1-2, Thread-0-3, Thread-1-3, Thread-0-4, Thread-1-4, Thread-0-5]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1, Thread-0-2, Thread-1-2, Thread-0-3, Thread-1-3, Thread-0-4, Thread-1-4, Thread-0-5, Thread-1-5]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1, Thread-0-2, Thread-1-2, Thread-0-3, Thread-1-3, Thread-0-4, Thread-1-4, Thread-0-5, Thread-1-5, Thread-0-6]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1, Thread-0-2, Thread-1-2, Thread-0-3, Thread-1-3, Thread-0-4, Thread-1-4, Thread-0-5, Thread-1-5, Thread-0-6, Thread-1-6]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1, Thread-0-2, Thread-1-2, Thread-0-3, Thread-1-3, Thread-0-4, Thread-1-4, Thread-0-5, Thread-1-5, Thread-0-6, Thread-1-6, Thread-0-7]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1, Thread-0-2, Thread-1-2, Thread-0-3, Thread-1-3, Thread-0-4, Thread-1-4, Thread-0-5, Thread-1-5, Thread-0-6, Thread-1-6, Thread-0-7, Thread-1-7]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1, Thread-0-2, Thread-1-2, Thread-0-3, Thread-1-3, Thread-0-4, Thread-1-4, Thread-0-5, Thread-1-5, Thread-0-6, Thread-1-6, Thread-0-7, Thread-1-7, Thread-0-8]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1, Thread-0-2, Thread-1-2, Thread-0-3, Thread-1-3, Thread-0-4, Thread-1-4, Thread-0-5, Thread-1-5, Thread-0-6, Thread-1-6, Thread-0-7, Thread-1-7, Thread-0-8, Thread-1-8]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1, Thread-0-2, Thread-1-2, Thread-0-3, Thread-1-3, Thread-0-4, Thread-1-4, Thread-0-5, Thread-1-5, Thread-0-6, Thread-1-6, Thread-0-7, Thread-1-7, Thread-0-8, Thread-1-8, Thread-0-9]
[Thread-0-0, Thread-1-0, Thread-0-1, Thread-1-1, Thread-0-2, Thread-1-2, Thread-0-3, Thread-1-3, Thread-0-4, Thread-1-4, Thread-0-5, Thread-1-5, Thread-0-6, Thread-1-6, Thread-0-7, Thread-1-7, Thread-0-8, Thread-1-8, Thread-0-9, Thread-1-9]
Process finished with exit code 0