List线程不安全解决办法和适用场景

List线程不安全解决办法和适用场景

ArrayList 多线程下的线程不安全问题

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class ListThreadUnsafeDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        // 10个线程同时向List添加元素
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    list.add(Thread.currentThread().getName() + "-" + j);
                }
            }).start();
        }

        // 等待所有线程执行完成(简单休眠,实际开发用CountDownLatch)
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 预期size=1000,实际大概率小于1000(数据丢失),或直接抛异常
        System.out.println("List最终长度:" + list.size());
    }
}

问题原因ArrayList底层是数组,写操作(add等)无锁保护,多线程同时修改数组大小、元素索引时,会出现操作覆盖索引错位,最终导致数据丢失或异常。

List 线程不安全的 2 种核心解决方案

使用CopyOnWriteArrayList(读多写少场景)

读多写少场景的最优解 ,核心原理是写时复制(Copy On Write)

  • 读操作:无锁,直接访问底层数组,多线程同时读不阻塞、高性能;
  • 写操作(add/remove/set):先加锁,再复制一份全新的底层数组,在新数组上执行修改,修改完成后将底层数组引用指向新数组,最后释放锁;
  • 遍历操作:基于原数组遍历,即使遍历中其他线程修改 List,也不会抛ConcurrentModificationException
java 复制代码
// 仅替换为CopyOnWriteArrayList,其余代码不变
List<String> list = new CopyOnWriteArrayList<>();

手动加锁保护普通 ArrayList(灵活可控,写多读少场景推荐)

核心是将多线程的非原子操作包裹在锁范围内,保证操作的原子性。

java 复制代码
for (int j = 0; j < 100; j++) {
                    // 核心:将写操作包裹在synchronized代码块中,锁对象为list本身
     synchronized (list) {
   		 list.add(Thread.currentThread().getName() + "-" + j);
 		}
}

使用ReentrantLock可重入锁(灵活,支持公平锁 / 非公平锁,推荐复杂场景)

synchronized更灵活,支持手动加锁 / 释放锁公平锁 (按线程等待顺序获取锁)、尝试获取锁tryLock()),适合需要精细控制锁的场景:

java 复制代码
private static final List<String> list = new ArrayList<>();
    // 定义可重入锁(默认非公平锁,传true为公平锁)
    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    lock.lock(); // 加锁
                    try {
                        // 核心操作:在锁保护下执行写操作
                        list.add(Thread.currentThread().getName() + "-" + j);
                    } finally {
                        lock.unlock(); // 释放锁(必须在finally中,防止异常导致锁泄漏)
                    }
                }
            }).start();
        }

拓展:复合操作的线程安全

复合操作指「多个 List 操作组合成的逻辑」(如判断是否为空 → 删除元素判断是否存在 → 修改元素),这类操作即使使用线程安全 List,也需要额外加锁,否则会出现线程安全问题。

java 复制代码
    private static final List<String> list = new CopyOnWriteArrayList<>();    
	public static void removeFirst() {
        // 复合操作:isEmpty() + remove(0),无锁保护,线程不安全
        if (!list.isEmpty()) {
            list.remove(0);
        }
    }
    public static void main(String[] args) {
        // 先添加元素
        list.add("A");
        list.add("B");

        // 2个线程同时执行removeFirst()
        new Thread(ListComplexOperationUnsafe::removeFirst).start();
        new Thread(ListComplexOperationUnsafe::removeFirst).start();
    }

问题 :两个线程可能同时通过isEmpty()判断,然后同时执行remove(0),导致索引越界异常。

java 复制代码
public static void removeFirst() {
        lock.lock();
        try {
            // 复合操作包裹在锁中,保证原子性
            if (!list.isEmpty()) {
                list.remove(0);
            }
        } finally {
            lock.unlock();
        }
    }
相关推荐
上海合宙LuatOS1 小时前
LuatOS框架的使用(1)
java·开发语言·单片机·嵌入式硬件·物联网·ios·iphone
会算数的⑨1 小时前
Spring AI Alibaba学习(一)—— RAG
java·人工智能·后端·学习·spring·saa
IT 行者1 小时前
Spring Security 7 响应头配置完全指南
java·后端·spring·security
bug-0071 小时前
springboot 自定义消息处理
java·spring boot·后端
我真的是大笨蛋1 小时前
MySQL临时表深度解析
java·数据库·sql·mysql·缓存·性能优化
九皇叔叔1 小时前
【02】微服务系列 之 初始化工程
java·数据库·微服务
季明洵1 小时前
两数之和、四数相加II、三数之和、四数之和
java·数据结构·算法·leetcode·蓝桥杯·哈希算法
人道领域1 小时前
javaWeb从入门到进阶(SpringBoot基础案例3)
java·spring boot·后端
西门吹-禅2 小时前
.gradle迁移d盘
java