java集合有蛮多的类型,今天我们以CopyOnWriteArrayList和Vector进行相关介绍。
CopyOnWriteArrayList
CopyOnWriteArrayList是Java集合框架中的一个线程安全的List实现类。它通过在修改操作时创建一个新的副本来实现线程安全性,因此称为"写时复制"。
Copy-On-Write简称COW,是一种用于程序设计中的优化策略。CopyOnWrite容器即写时复制的容器。通俗的理解是当往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
CopyOnWrite并发容器用于读多写少的并发场景。
复制用法导致内存占用可能过高。
保证数据最终一致性,无法保证实时一致性。
特点
-
线程安全:CopyOnWriteArrayList是线程安全的,多个线程可以同时读取列表中的元素,而不需要额外的同步措施。这使得它非常适合在多线程环境下进行读取操作。
-
写时复制:当有线程对CopyOnWriteArrayList进行修改操作(如添加、删除元素)时,它会创建一个新的副本,并在副本上进行修改操作。这样,其他线程仍然可以在原始列表上进行读取操作,不会受到修改操作的影响。一旦修改完成,新的副本会替换原始列表,以确保修改的一致性。
-
高效的读取操作:由于读取操作不需要进行同步,CopyOnWriteArrayList在读取操作上具有很高的性能。这使得它非常适合在读多写少的场景中使用。
-
低效的写入操作:由于每次写入操作都需要创建一个新的副本,CopyOnWriteArrayList在写入操作上的性能相对较低。因此,如果应用程序中有大量的写入操作,可能会影响性能。
-
迭代器的弱一致性:CopyOnWriteArrayList的迭代器提供了弱一致性的保证。即,迭代器在创建时会获取一个快照,并在迭代过程中遍历该快照。这意味着迭代器不会反映出在迭代过程中对列表所做的修改。
CopyOnWriteArrayList适用于读多写少的场景,特别是在需要保证线程安全性的情况下。它常用于事件监听器列表、缓存等场景,其中读取操作远远超过写入操作。
java
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// 添加元素
list.add("Apple");
list.add("Banana");
list.add("Orange");
// 遍历元素
for (String element : list) {
System.out.println(element);
}
// 修改元素
list.set(1, "Grape");
// 删除元素
list.remove("Apple");
// 遍历元素
for (String element : list) {
System.out.println(element);
}
}
CopyOnWriteArrayList是如何保证安全的
CopyOnWriteArrayList通过"写时复制"(Copy-On-Write)机制来保证线程安全性。具体来说,当有线程对CopyOnWriteArrayList进行修改操作时,它会创建一个新的副本,并在副本上进行修改操作 。这样,其他线程仍然可以在原始列表上进行读取操作,不会受到修改操作的影响。一旦修改完成,新的副本会替换原始列表,以确保修改的一致性。
这种机制的实现步骤如下:
-
当有线程要对CopyOnWriteArrayList进行修改操作时,它首先会创建一个当前列表的副本。
-
在副本上进行修改操作,例如添加、删除元素等。
-
修改完成后,将新的副本替换原始列表,使得其他线程可以看到最新的修改。
通过这种方式,CopyOnWriteArrayList实现了线程安全性,因为每个线程都在自己的副本上进行修改操作,不会影响其他线程的读取操作。这样就避免了传统的同步机制(如锁)带来的竞争和阻塞。
需要注意的是,由于每次修改操作都会创建一个新的副本,CopyOnWriteArrayList在写入操作上的性能相对较低。因此,它更适合于读多写少的场景,特别是在需要保证线程安全性的情况下。
代码层次分析
我们可以从代码层面进行相关分析:
-
写操作的实现:CopyOnWriteArrayList的写操作(如添加、删除元素)是通过创建一个新的副本来实现的。在添加元素时,会创建一个新的数组,并将原始数组中的元素复制到新数组中,然后在新数组中添加新元素。删除元素时,也是创建一个新的数组,并将原始数组中的元素复制到新数组中,但是不包括要删除的元素。最后,将新数组替换原始数组。
-
读操作的实现:CopyOnWriteArrayList的读操作是在原始数组上进行的,不需要额外的同步措施。这是因为在写操作期间,读操作仍然可以访问原始数组,不会受到写操作的影响。这样可以保证读操作的线程安全性。
-
使用volatile关键字:CopyOnWriteArrayList内部使用了volatile关键字来保证多线程之间的可见性。当一个线程修改了列表时,它会将新的副本赋值给volatile修饰的数组引用,以便其他线程可以看到最新的修改。
-
迭代器的一致性:CopyOnWriteArrayList的迭代器提供了弱一致性的保证。即,迭代器在创建时会获取一个快照,并在迭代过程中遍历该快照。这意味着迭代器不会反映出在迭代过程中对列表所做的修改。
CopyOnWriteArrayList实现了线程安全性。每个线程在修改操作时都会在自己的副本上进行操作,不会影响其他线程的读取操作。这样就避免了传统的同步机制(如锁)带来的竞争和阻塞,提供了高效的线程安全的List实现。
案例说明
假设我们有一个任务列表,多个线程同时对任务列表进行读取和修改操作。我们使用CopyOnWriteArrayList来存储任务列表,并保证线程安全。
java
public class TaskList {
private CopyOnWriteArrayList<String> tasks = new CopyOnWriteArrayList<>();
public void addTask(String task) {
tasks.add(task);
}
public void removeTask(String task) {
tasks.remove(task);
}
public void printTasks() {
for (String task : tasks) {
System.out.println(task);
}
}
}
我们创建多个线程来对任务列表进行读取和修改操作:
java
public class Main {
public static void main(String[] args) {
TaskList taskList = new TaskList();
// 创建多个线程进行读取和修改操作
Thread readerThread1 = new Thread(() -> {
taskList.printTasks();
});
Thread readerThread2 = new Thread(() -> {
taskList.printTasks();
});
Thread writerThread1 = new Thread(() -> {
taskList.addTask("Task 1");
});
Thread writerThread2 = new Thread(() -> {
taskList.removeTask("Task 1");
});
// 启动线程
readerThread1.start();
readerThread2.start();
writerThread1.start();
writerThread2.start();
}
}
我们创建了两个读取线程(readerThread1和readerThread2)和两个修改线程(writerThread1和writerThread2)。读取线程通过调用printTasks方法来打印任务列表,修改线程通过调用addTask和removeTask方法来添加和删除任务。
由于CopyOnWriteArrayList的线程安全性,多个线程可以同时读取和修改任务列表,而不需要额外的同步措施。读取线程可以在修改线程进行操作的同时访问任务列表,并且不会受到修改操作的影响。
我们可以看到CopyOnWriteArrayList的线程安全性。每个线程在修改操作时都会在自己的副本上进行操作,不会影响其他线程的读取操作。这样就实现了多线程环境下的安全访问和修改任务列表。