什么是CopyOnWriteArrayList和CopyOnWriteArraySet
CopyOnWriteArrayList和CopyOnWriteArraySet都是Java并发编程中提供的线程安全的集合类。
CopyOnWriteArrayList是一个线程安全的ArrayList,其内部通过volatile数组和显式锁ReentrantLock来实现线程安全。它采用了一种称为“写入时复制”(Copy-On-Write)的策略,即当进行修改操作时,会先复制一份当前数据,然后在新的数据上进行修改,修改完成后再将指向原来数据的引用指向新的数据。这样可以保证读操作可以并发进行而不会被阻塞,同时也保证了数据的线程安全性。
CopyOnWriteArraySet是一个线程安全的Set,其内部实现是基于CopyOnWriteArrayList的。它持有一个CopyOnWriteArrayList的引用,所有的操作都是由这个CopyOnWriteArrayList来实现的。与CopyOnWriteArrayList不同的是,CopyOnWriteArraySet是无序的,并且不允许存放重复值。
由于CopyOnWriteArrayList和CopyOnWriteArraySet的写操作都需要复制底层数组,因此如果集合较大或者写操作非常频繁,那么这种实现方式的性能可能会比较低。因此,在选择使用这些集合类时需要根据具体的应用场景进行权衡。
另外,需要注意的是,虽然CopyOnWriteArrayList和CopyOnWriteArraySet的读操作可以在不加锁的情况下进行,但是并不是所有的读操作都是完全无锁的。例如,迭代器在迭代过程中会持有底层数组的引用,如果在这个过程中发生了写操作,那么迭代器可能会抛出ConcurrentModificationException异常。因此,在使用迭代器进行遍历时需要特别注意。
代码示例
下面是使用CopyOnWriteArrayList和CopyOnWriteArraySet的简单代码示例:
CopyOnWriteArrayList示例
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;public class CopyOnWriteArrayListExample {public static void main(String[] args) {// 创建一个CopyOnWriteArrayListList<String> list = new CopyOnWriteArrayList<>();// 添加元素list.add("Element 1");list.add("Element 2");list.add("Element 3");// 在新的线程中修改列表new Thread(() -> {list.add("Element 4"); // 线程安全地添加元素}).start();// 主线程中迭代列表for (String element : list) {System.out.println(element); // 读取操作也是线程安全的}}
}
CopyOnWriteArraySet示例
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;public class CopyOnWriteArraySetExample {public static void main(String[] args) {// 创建一个CopyOnWriteArraySetSet<String> set = new CopyOnWriteArraySet<>();// 添加元素set.add("Element 1");set.add("Element 2");set.add("Element 3");// 在新的线程中修改集合new Thread(() -> {set.add("Element 4"); // 线程安全地添加元素}).start();// 主线程中迭代集合for (String element : set) {System.out.println(element); // 读取操作也是线程安全的}}
}
在上面的两个示例中,CopyOnWriteArrayList和CopyOnWriteArraySet都是在多线程环境中安全使用的。即使在修改集合(添加元素)的同时,其他线程也可以安全地迭代集合。这是因为写操作(如添加元素)会创建一个集合的新副本,并在修改完成后替换旧副本。这样,读取操作可以安全地继续进行,而不会受到写操作的干扰。
需要注意的是,由于写操作需要复制整个底层数组,因此如果集合非常大,或者写操作非常频繁,性能可能会受到影响。在这种情况下,可能需要考虑使用其他并发集合,如ConcurrentHashMap的keySet或entrySet视图,或者自定义的并发解决方案。