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

Java WebSocket白板服务端报ConcurrentModificationException异常如何解决

异常原因

你遇到的ConcurrentModificationException是Java集合迭代的经典并发问题,根因如下:

  • 你用Collections.synchronizedSet包装的HashSet仅能保证add、remove等单个操作的原子性,增强for循环(本质是迭代器遍历)不会自动持有集合锁
  • 你在遍历peers集合的同时,其他线程可能会因为新客户端连接、旧客户端断开,调用@OnOpen/@OnClose修改集合结构,触发迭代器的fail-fast机制抛出异常

解决方案

方案1:遍历前对集合加锁(推荐,一致性最高)

按照Collections.synchronizedSet的官方文档要求,遍历集合时手动对集合对象本身加锁,保证遍历期间集合不会被修改,修改后的broadcastShape代码如下:

@OnMessage
public void broadcastShape(Drawing drawing, Session session) throws IOException, EncodeException {
    // 对peers对象加锁,阻塞遍历期间的add/remove操作
    synchronized (peers) {
        for (Session peer : peers) {
            if (!peer.equals(session) && peer.isOpen()) {
                peer.getAsyncRemote().sendObject(drawing);
            }
        }
    }
}

方案2:遍历集合快照(性能更高,无锁)

先将当前集合的元素复制为独立数组/新集合,遍历的是副本,原集合的修改不会影响遍历过程:

@OnMessage
public void broadcastShape(Drawing drawing, Session session) throws IOException, EncodeException {
    // 生成当前集合的快照,后续原集合修改与快照无关
    Session[] peerSnapshot = peers.toArray(new Session[0]);
    for (Session peer : peerSnapshot) {
        if (!peer.equals(session) && peer.isOpen()) {
            peer.getAsyncRemote().sendObject(drawing);
        }
    }
}

该方案不需要加锁,不会阻塞新用户连接/断开的操作,适合高并发场景,仅存在极短时间的一致性差异(快照生成后刚断开的Session可能会收到一次无效消息,加peer.isOpen()判断即可规避副作用)。

额外优化建议

可以在发送消息时增加结果校验,自动移除无效Session避免内存泄漏:

peer.getAsyncRemote().sendObject(drawing, result -> {
    if (!result.isOK()) {
        peers.remove(peer);
        try {
            peer.close();
        } catch (IOException ignore) {}
    }
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 17:15:00