java中在多线程的情况下安全的修改list(常见解决方案)

 更新时间:2025年09月04日 09:27:39   作者:藤原とラふ店丶  
本文给大家介绍java中在多线程的情况下安全的修改list的常见解决方案及实现方式,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

在Java中,ArrayListLinkedList等常见List实现类不是线程安全的(非同步)。当多个线程同时对其进行修改(如addremove)或读写操作时,可能会导致数据不一致、ConcurrentModificationException(并发修改异常)等问题。

要在多线程环境下安全地修改List,需通过线程安全的容器同步机制保证操作的原子性和可见性。以下是常用解决方案及实现方式:

一、使用线程安全的List实现类

Java提供了几种线程安全的List实现,可直接替换非线程安全的List,无需手动处理同步。

1.Vector(古老实现,不推荐)

Vector是Java早期的线程安全List实现,其所有方法都被synchronized修饰(同步方法),保证线程安全。
缺点:同步粒度太粗(整个方法加锁),多线程并发效率低,且功能上被更优的方案替代,不推荐在新代码中使用

// Vector是线程安全的,但性能较差
List<String> vector = new Vector<>();
// 多线程可安全调用add/remove等方法
vector.add("A");
vector.remove(0);

2.Collections.synchronizedList()(包装同步,推荐基础场景)

Collections工具类的synchronizedList()方法可将任意非线程安全的List包装为线程安全的List。其原理是对所有方法添加同步锁(使用synchronized块),保证同一时刻只有一个线程能操作List

使用方式

// 1. 创建非线程安全的List(如ArrayList)
List<String> unsafeList = new ArrayList<>();
// 2. 包装为线程安全的List
List<String> safeList = Collections.synchronizedList(unsafeList);
// 多线程环境下可安全操作
// 线程1:添加元素
new Thread(() -> {
    safeList.add("A");
}).start();
// 线程2:删除元素
new Thread(() -> {
    if (!safeList.isEmpty()) {
        safeList.remove(0);
    }
}).start();

注意事项

迭代操作需手动加锁:synchronizedList返回的List在迭代时(如for-eachiterator不自动同步,需手动用synchronized块包裹,否则可能抛出ConcurrentModificationException

// 迭代时必须手动同步(锁对象为safeList本身)
synchronized (safeList) {
    for (String s : safeList) {
        System.out.println(s);
    }
}

适合读写频率均衡的场景:由于所有操作都加锁,高并发下性能一般,但实现简单,适合大多数基础场景。

3.CopyOnWriteArrayList(写时复制,推荐读多写少场景)

CopyOnWriteArrayList是Java并发包(java.util.concurrent)提供的线程安全List,其核心原理是**“写时复制”**:

  • 读操作:无需加锁,直接访问当前数组(性能极高)。
  • 写操作(addremove等):先复制一份新的数组,在新数组上修改,然后将引用指向新数组(修改时加锁,保证原子性)。

适用场景:读操作远多于写操作(如缓存、配置列表),写操作频率低但读操作需高效。

使用方式

import java.util.concurrent.CopyOnWriteArrayList;
// 初始化线程安全的CopyOnWriteArrayList
List<String> cowList = new CopyOnWriteArrayList<>();
// 多线程安全操作
// 线程1:添加元素(写操作,会复制数组)
new Thread(() -> {
    cowList.add("A");
}).start();
// 线程2:读取元素(读操作,无锁,直接访问)
new Thread(() -> {
    for (String s : cowList) {
        System.out.println(s);
    }
}).start();

优点

  • 读操作无锁,并发性能极佳(适合读多写少)。
  • 迭代时不会抛出ConcurrentModificationException(迭代的是旧数组的快照)。

缺点

  • 写操作成本高(复制数组,内存占用翻倍)。
  • 数据实时性差(读操作可能访问的是旧数组,修改后的数据需等新数组替换后才能被读取)。

二、手动同步(锁机制)

如果需要更灵活地控制同步粒度(如仅对关键修改操作加锁),可使用synchronized关键字或Lock接口手动实现同步。

1. 使用synchronized块

通过synchronized锁定List对象或其他锁对象,保证同一时刻只有一个线程执行修改操作。

List<String> list = new ArrayList<>();
// 定义锁对象(也可直接用list本身作为锁)
Object lock = new Object();
// 线程1:添加元素
new Thread(() -> {
    synchronized (lock) { // 加锁
        list.add("A");
    }
}).start();
// 线程2:删除元素
new Thread(() -> {
    synchronized (lock) { // 加锁
        if (!list.isEmpty()) {
            list.remove(0);
        }
    }
}).start();

2. 使用ReentrantLock(可重入锁)

java.util.concurrent.locks.ReentrantLock提供比synchronized更灵活的锁控制(如超时锁、公平锁),适合复杂场景。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
List<String> list = new ArrayList<>();
// 创建锁对象(可指定为公平锁,按请求顺序获取锁)
Lock lock = new ReentrantLock(true);
// 线程1:添加元素
new Thread(() -> {
    lock.lock(); // 加锁
    try {
        list.add("A");
    } finally {
        lock.unlock(); // 必须在finally中释放锁,避免死锁
    }
}).start();
// 线程2:删除元素
new Thread(() -> {
    lock.lock(); // 加锁
    try {
        if (!list.isEmpty()) {
            list.remove(0);
        }
    } finally {
        lock.unlock();
    }
}).start();

三、注意事项

  • 复合操作的原子性
    • 即使使用线程安全的List复合操作(如“先判断再修改”)仍需额外同步。例如:
// 错误示例:contains和add是两个独立操作,可能被其他线程打断
if (!safeList.contains("A")) { 
    safeList.add("A"); // 可能重复添加
}
// 正确:用同步块保证复合操作原子性
synchronized (safeList) {
    if (!safeList.contains("A")) {
        safeList.add("A");
    }
}
  • 迭代器的线程安全
    • synchronizedList的迭代器需手动同步(见上文)。
    • CopyOnWriteArrayList的迭代器是“快照迭代器”,不支持removeadd等修改操作(会抛UnsupportedOperationException),只能遍历。
  • 性能权衡
    • 读多写少:优先CopyOnWriteArrayList(读无锁)。
    • 读写均衡或写操作频繁:优先Collections.synchronizedList()或手动锁(避免CopyOnWriteArrayList的复制开销)。
    • 避免使用Vector(性能差,已过时)。

总结

多线程安全修改List的核心是保证操作的原子性和可见性,常用方案对比:

方案原理优点缺点适用场景
Vector同步方法简单直接性能差,同步粒度粗兼容旧代码(不推荐新用)
synchronizedList同步块包装适配所有List,实现简单所有操作加锁,并发性能一般读写均衡的基础场景
CopyOnWriteArrayList写时复制读操作无锁,性能极佳写操作成本高,数据实时性差读多写少(如缓存、配置)
手动锁(synchronized/Lock自定义同步粒度灵活控制锁范围需手动处理锁释放,易出错复杂场景(如复合操作)

根据实际业务的读写频率和复杂度选择合适方案即可。

到此这篇关于java中在多线程的情况下安全的修改list(常见解决方案)的文章就介绍到这了,更多相关java多线程修改list内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详细分析java并发之volatile关键字

    详细分析java并发之volatile关键字

    这篇文章主要介绍了java并发之volatile关键字的的相关资料,文中代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-06-06
  • HttpClient详细使用示例代码

    HttpClient详细使用示例代码

    这篇文章主要介绍了HttpClient详细使用示例,包括导入依赖,使用工具类的详细代码,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-07-07
  • SpringBoot实现其他普通类调用Spring管理的Service,dao等bean

    SpringBoot实现其他普通类调用Spring管理的Service,dao等bean

    这篇文章主要介绍了SpringBoot实现其他普通类调用Spring管理的Service,dao等bean,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Java 批量文件压缩导出并下载到本地示例代码

    Java 批量文件压缩导出并下载到本地示例代码

    这篇文章主要介绍了Java 批量文件压缩导出并下载到本地示例代码,实现思路首先要把zip流写入到http响应输出流中,再把excel的流写入zip流中,具体示例代码,大家通过本文学习吧
    2017-12-12
  • java中Arthas的作用和使用方式

    java中Arthas的作用和使用方式

    Arthas 是由阿里巴巴开源的 Java 诊断工具,主要用于在生产环境中诊断和解决 Java 应用问题,它提供实时监控、类信息查看、动态追踪、条件表达式断点等功能,支持非侵入式设计和高扩展性,通过 Arthas,开发者可以在不停止应用的情况下,快速定位和解决问题
    2025-01-01
  • Spring Boot 之HelloWorld开发案例

    Spring Boot 之HelloWorld开发案例

    这篇文章主要介绍了Spring Boot 之HelloWorld开发案例,需要的朋友可以参考下
    2017-04-04
  • IntelliJ IDEA快速查看某个类/接口的子类或父类

    IntelliJ IDEA快速查看某个类/接口的子类或父类

    本文主要介绍了IntelliJ IDEA快速查看某个类/接口的子类或父类,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • DUCC配置平台实现一个动态化线程池示例代码

    DUCC配置平台实现一个动态化线程池示例代码

    这篇文章主要为大家介绍了DUCC配置平台实现一个动态化线程池示例代码,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • Java 中限制方法的返回时间最新方法

    Java 中限制方法的返回时间最新方法

    最近在研究 ChatGPT 的 API 调用,因为 ChatGPT 的 API 调用时间通常超过 30 秒,所以我们希望在程序中限制这个方法的执行时间,不要让方法花太长时间去执行了,今天通过本文给大家分享Java 中如何限制方法的返回时间,感兴趣的朋友跟随小编一起看看吧
    2023-05-05
  • Java序列化常见的三个问题

    Java序列化常见的三个问题

    这篇文章主要介绍了Java序列化常见的三个问题,帮助大家更好的理解和学习JAVA,感兴趣的朋友可以了解下
    2020-08-08

最新评论