簡體   English   中英

synchronized() 代碼上的 ConcurrentModificationException - 這怎么可能?

[英]ConcurrentModificationException on synchronized() code - how is it possible?

我有一個以下 spring bean(稍微編輯)這個 bean 負責添加/刪除 websocket 連接到內部連接(this.sockets)。 每次需要發送消息時,都可以添加/刪除新的連接並對其進行迭代。

正如您所看到的, sockets字段上的每個操作都包含在一個synchronized()塊中,它是私有的,不會在這個 class 之外使用,這個字段的每次使用都在我粘貼在下面的代碼中。

但我仍然不時收到在onApplicationEvent調用的 for 循環中發生的ConcurrentModificartionException 這個調用可能確實被應用程序中的多個線程調用,但我的理解是,如果每個實際修改或迭代this.socket變量的調用都包裝在同一個 object 上的 synchronized() 塊中,那么這些調用中的每一個都不能在並行並且必須相互“等待”。

這段代碼怎么可能拋出ConcurrentModificationException 請記住,我正在嘗試了解出現異常的原因,並且我並不是在尋找一個快速解決方案,它只會解決這個問題 go(所以像“你為什么不在這里使用 XXX”這樣的回答實際上並沒有解決任何問題我 - 我試圖找出我在代碼中做錯了什么)。

代碼:

public class MyClass extends TextWebSocketHandler implements ApplicationListener<AppEventMessage>{

    private static final Logger log= LoggerFactory.getLogger(MyClass.class);

    private Set<WebSocketSession> sockets=new HashSet<>();


    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        super.afterConnectionEstablished(session);
        synchronized (this.sockets) {
            this.sockets.add(session);
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        synchronized (this.sockets) {
            this.sockets.remove(session);
        }
    }

    @Override
    public void onApplicationEvent(AppEventMessage event) {
        synchronized (this.sockets) {
            for (WebSocketSession session : this.sockets) {
                try {
                    log.info("sendin");
                    session.sendMessage(new TextMessage(event.getMessage()));
                } catch (Throwable e) {
                    log.warn("error");
                }
            }
        }
    }
}

stacktrace 最相關的部分(附上我的評論)

java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
    at java.util.HashMap$KeyIterator.next(HashMap.java:1469)
    at ...MyClass.onApplicationEvent(ApplicationEventWebsocketHandler.java:46) <- (line 46 is the for(WebSocketSession ...) line)
    at ...MyClass.onApplicationEvent(ApplicationEventWebsocketHandler.java:19) <- (line 19 is the "public class Myclass" line - how it is possible?)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:163)

目前同意你的診斷。 還有第二種方法可以獲取通常被忽視的ConcurrentModificationException ......

這些synchronized鎖是可重入的,這意味着在一個方法中持有鎖的線程可以訪問所有方法。 因此,導致 CME 的線程很可能是for循環中的回調,最終返回到連接偵聽器方法中。

IMO,最可能的情況是連接僅在嘗試sendMessage時才被檢測為已關閉。

解決方案:有更聰明的解決方案,但這是一個不會 go 錯誤的解決方案:

  // Take copy of sockets before iterating it to defend against self-induced ConcurrentModificationException
  for (WebSocketSession session : new HashSet<>(this.sockets)) {

我沒有立即看到什么可能導致ConcurrentModificationException ,但這里有一個可能有幫助的建議。 而不是同步this.sockets在你的 class a 中聲明
static final Object myLock = new Object();
並同步。 這個 object 將是唯一且不可修改的。 更好的候選人是鎖。 更好的是,閱讀SemaphoreLock類。 它們提供更好的同步並且更易於維護

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM