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
相关产品推荐
相关产品推荐

