You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

序列化SynchronizedCollection包装的ArrayList时抛出ConcurrentModificationException

问题原因分析

你遇到的ConcurrentModificationException,核心问题出在**Collections.synchronizedCollection的默认序列化逻辑没有在整个序列化流程中持有锁**。

看一下Collections$SynchronizedCollection的writeObject源码实现:

private void writeObject(ObjectOutputStream s) throws IOException {
    synchronized (mutex) {
        s.defaultWriteObject();
    }
}

这个方法只在执行s.defaultWriteObject()时加锁——这一步仅仅是把SynchronizedCollection自身的字段(比如底层的ArrayList实例c)写入流。等这行代码执行完毕,锁就被释放了。

而真正序列化底层ArrayList的操作,是在锁释放之后才触发的:ObjectOutputStream接下来会去序列化刚才写出的c字段,调用ArrayList自己的writeObject方法。这时候其他线程完全可以调用list.add()(因为add方法的同步锁是同一个mutex,但锁已经被释放),导致ArrayList在序列化过程中被修改,触发ConcurrentModificationException。

解决方案

要解决这个问题,需要确保整个序列化过程都持有SynchronizedCollection的锁,有两种可行的实现方式:

1. 序列化前手动加锁

在调用oos.writeObject(list)前后手动获取并释放锁,把整个序列化过程包裹在锁的保护范围内:

// 修改WriteListTask的run方法
@Override
public void run() {
    File file = new File("temp");
    OutputStream os = null;
    ObjectOutputStream oos = null;
    try {
        os = new FileOutputStream(file);
        oos = new ObjectOutputStream(os);
        // 手动获取锁,list是SynchronizedList,锁对象就是自身
        synchronized (list) {
            oos.writeObject(list);
        }
        oos.flush();
        os.flush();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (oos != null) oos.close();
            if (os != null) os.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    System.out.println(String.format("write [%d] list job done", ++writeTaskNo));
}

这样整个序列化流程(包括底层ArrayList的writeObject执行)都会在锁的保护下,其他线程的修改操作会被阻塞,直到序列化完成。

2. 自定义同步集合包装类

如果不想每次序列化都手动加锁,可以自己实现一个集合包装类,让它的序列化逻辑全程持有锁:

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;

public class SynchronizedSerialList<E> implements List<E>, Serializable {
    private final List<E> delegate;
    private final Object mutex;

    public SynchronizedSerialList(List<E> delegate) {
        this.delegate = Objects.requireNonNull(delegate);
        this.mutex = this;
    }

    // 实现所有List接口方法,都用synchronized(mutex)包裹,参考Collections.synchronizedList的实现
    @Override
    public boolean add(E e) {
        synchronized (mutex) {
            return delegate.add(e);
        }
    }

    // 省略其他List方法的实现,均按上述方式同步

    // 自定义writeObject,确保序列化全程持有锁
    private void writeObject(ObjectOutputStream s) throws IOException {
        synchronized (mutex) {
            s.writeObject(delegate);
        }
    }

    private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
        synchronized (mutex) {
            delegate = (List<E>) s.readObject();
        }
    }
}

然后把原来的Collections.synchronizedList替换成这个自定义类:

private static final List<String> list = new SynchronizedSerialList<>(new ArrayList<>());
验证

修改后运行你的测试代码,就不会再出现ConcurrentModificationException了——序列化全程被锁保护,其他线程的修改操作会被阻塞直到序列化完成。

内容的提问来源于stack exchange,提问作者Shouheng Wang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 13:10:24