CopyOnWriteArrayList 是 Java 中的一个线程安全变体的 ArrayList,它用于读多写少的场景。在 CopyOnWriteArrayList 中,所有的修改操作(如添加、删除元素)都会在数组的副本上进行,修改完成后再将原数组引用指向新的副本。这种方法可以减少锁的使用,提高读操作的性能,因为在读取时不需要加锁。由于写操作需要复制数组,所以当列表元素较多或写操作频繁时,可能会影响性能。CopyOnWriteArrayList 适用于迭代操作远多于修改操作的场景,如并发缓存、实时查询结果的存储等。
1、CopyOnWriteArrayList
CopyOnWriteArrayList 在 Java 中是一个线程安全的变体数组列表,其特点是在修改(写操作)时通过复制整个底层数组来实现,以此保证读操作的线程安全和高性能。以下是 CopyOnWriteArrayList 的设计:
设计思考:
- 需求场景:
- 在多线程环境中,读操作远比写操作频繁,且对数据的实时性要求不是非常高的场景。例如,缓存系统、实时数据的订阅发布模型等。
- 现有技术局限性:
- 传统的线程安全实现,如
Vector或通过synchronized同步代码块或方法,可能会因为写操作导致的线程阻塞,严重影响并发性能。
- 传统的线程安全实现,如
- 技术融合:
CopyOnWriteArrayList采用了写时复制(Copy-On-Write)的策略,当进行写操作(添加、删除等)时,先复制整个数组,然后在新数组上进行操作,而读操作则直接作用于原数组,从而提高了读操作的性能。
- 设计理念:
- 利用了读操作远多于写操作的特性,通过分离读和写操作,使得读操作无需加锁,从而提高了并发读的性能。
- 实现方式:
- 内部使用一个数组来存储元素,所有写操作都会创建一个新的数组,并将修改应用于新数组,然后原子性地将内部数组引用指向新数组。
2、 数据结构
图说明:
- CopyOnWriteArrayList:
- 表示
CopyOnWriteArrayList的实例。
- 表示
- Object[] array:
CopyOnWriteArrayList内部使用的一个数组array来存储元素。这是原始数组,所有读操作都访问这个数组。
- Object[] newArray:
- 写操作时创建的新数组。当写操作发生时,这个数组是原始数组的一个深拷贝。
- 写操作:
- 包括添加、删除或修改元素。写操作不是在原始数组上进行,而是在新数组上进行。
工作原理:
- 读操作:
- 多个读线程可以同时访问和遍历
array,因为数组是不可变的。
- 多个读线程可以同时访问和遍历
- 写操作:
- 当写操作发生时(如添加、删除或修改元素),写线程首先会创建原始
array的一个副本newArray。 - 写线程在
newArray上进行添加、删除或修改操作。 - 写操作完成后,写线程会原子性地将
CopyOnWriteArrayList的内部数组引用指向newArray。
- 当写操作发生时(如添加、删除或修改元素),写线程首先会创建原始
- 数据一致性:
- 在写操作进行时,读线程仍然可以访问旧的内部数组
array,从而保证了数据的一致性。
- 在写操作进行时,读线程仍然可以访问旧的内部数组
3、 执行流程
图说明:
- 初始化 CopyOnWriteArrayList:
- 创建一个空的
CopyOnWriteArrayList实例。
- 创建一个空的
- 内部数组 array:
CopyOnWriteArrayList内部使用一个数组来存储元素。
- 读操作:
- 直接读取内部数组的元素,是线程安全的,因为内部数组不可变。
- 写操作:
- 包括添加、删除和修改元素,需要创建内部数组的一个新副本。
- 复制数组:
- 在执行写操作前,复制内部数组,以保证新元素的添加不会影响读操作。
- 添加元素:
- 向
CopyOnWriteArrayList添加新元素。
- 向
- 删除元素:
- 从
CopyOnWriteArrayList删除元素。
- 从
- 修改元素:
- 修改
CopyOnWriteArrayList中的元素。
- 修改
- 数组拷贝:
- 创建内部数组的一个新副本,并在新副本上执行写操作。
4、优点:
- 读操作性能高:
- 读操作不需要加锁,可以由多个线程并发进行。
- 线程安全:
- 写操作通过复制整个数组来保证线程安全。
- 写操作不阻塞读操作:
- 写操作不会影响读操作的执行,因为读操作作用于原数组。
5、缺点:
- 写操作性能开销:
- 写操作需要复制整个数组,对于大数据量的数组,性能开销较大。
- 内存消耗:
- 写操作时会创建新的数组,可能会导致内存消耗较高。
- 数据实时性:
- 读操作可能读取到的不是最新数据,因为写操作完成后,数据的变更才会被复制到新数组。
6、使用场景:
- 适用于读多写少的场景,如配置信息的存储、统计信息的收集等。
7、类设计
8、应用案例
CopyOnWriteArrayList 通常用于实现线程安全的动态数组,特别是在读操作远多于写操作的情况下。这是一个简单的实时消息系统,用于管理在线用户列表:
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.List;
// 用户类,用于表示系统中的用户
class User {
private String username;
private String status;
public User(String username, String status) {
this.username = username;
this.status = status;
}
public String getUsername() {
return username;
}
public String getStatus() {
return status;
}
@Override
public String toString() {
return username + " (" + status + ")";
}
}
// 消息系统类,用于管理在线用户列表
class MessagingSystem {
private List<User> onlineUsers;
public MessagingSystem() {
onlineUsers = new CopyOnWriteArrayList<>();
}
// 添加用户到在线列表
public void addUser(User user) {
onlineUsers.add(user);
System.out.println("User added: " + user);
}
// 移除用户从在线列表
public void removeUser(User user) {
onlineUsers.remove(user);
System.out.println("User removed: " + user);
}
// 获取当前所有在线用户
public List<User> getOnlineUsers() {
return onlineUsers;
}
}
public class Main {
public static void main(String[] args) {
MessagingSystem messagingSystem = new MessagingSystem();
// 模拟添加用户到系统
messagingSystem.addUser(new User("Alice", "online"));
messagingSystem.addUser(new User("Bob", "online"));
messagingSystem.addUser(new User("Charlie", "online"));
// 获取并打印所有在线用户
List<User> onlineUsers = messagingSystem.getOnlineUsers();
for (User user : onlineUsers) {
System.out.println("Online user: " + user);
}
// 模拟移除用户
messagingSystem.removeUser(new User("Bob", "online"));
}
}