• 并发Map类删除数据的问题java.lang.UnsupportedOperationException: null


    背景

    笔者最近在开发Websocket相关的消息推送服务,使用了JSR356规范,由于需要维持会话。于是分别使用了以下类

    //客户端缓存<dispatchNo,ebSocketServer>
        private static final ConcurrentHashMap<String, Set<WebSocketServer>> clientRegistry = new ConcurrentHashMap<>();
     //客户端缓存<dispatchNo,ebSocketServer>
        private static final ConcurrentMap<String, Set<WebSocketServer>> clientRegistry = new ConcurrentSkipListMap<>();

    笔者在维护失效的代码写了以下代码

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        for(final Iterator<Map.Entry<String, Set<WebSocketServer>>> iterator = clientRegistry.entrySet().iterator();
                    iterator.hasNext();)
        {
            final Map.Entry<String, Set<WebSocketServer>> entry = iterator.next();
            final String dispatchNo = entry.getKey();
            final Set<WebSocketServer> clients = entry.getValue();
    
            for(final Iterator<WebSocketServer> clients = entry.getValue().iterator();
                clients.hasNext();) {
    
                final WebSocketServer client = clients.next();
                if (Objects.equals(client, this)) {
                    // 断开连接情况下,更新主板占用情况为释放
                    log.info("session:{}取消订阅工序派工单{}的产生数据", session.getId(), dispatchNo);
                    clients.remove();
                } else {
    
                    final Session session = client.session;
                    final boolean isAlive = session.isOpen();
                    if (!isAlive) {
                        clients.remove();
                    }
                }
            }
        }
    }

    最终在调用clients.remove();时候报错

    java.lang.UnsupportedOperationException: null
        at java.util.concurrent.CopyOnWriteArrayList$COWIterator.remove(CopyOnWriteArrayList.java:1178) ~[?:1.8.0_202]
        at com.xxxx.mes.websocket.WebSocketServer.onClose(WebSocketServer.java:192) ~[classes/:?]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_202]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_202]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_202]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_202]
        at io.undertow.websockets.jsr.annotated.BoundMethod.invoke(BoundMethod.java:87) ~[undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
        at io.undertow.websockets.jsr.annotated.AnnotatedEndpoint$4.run(AnnotatedEndpoint.java:201) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
        at io.undertow.websockets.jsr.ServerWebSocketContainer$1.call(ServerWebSocketContainer.java:170) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
        at io.undertow.websockets.jsr.ServerWebSocketContainer$1.call(ServerWebSocketContainer.java:167) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
        at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) [undertow-servlet-2.0.32.Final.jar:2.0.32.Final]
        at io.undertow.websockets.jsr.ServerWebSocketContainer.invokeEndpointMethod(ServerWebSocketContainer.java:610) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
        at io.undertow.websockets.jsr.ServerWebSocketContainer.invokeEndpointMethod(ServerWebSocketContainer.java:600) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
        at io.undertow.websockets.jsr.annotated.AnnotatedEndpoint.onClose(AnnotatedEndpoint.java:196) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
        at io.undertow.websockets.jsr.UndertowSession.closeInternal(UndertowSession.java:235) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
        at io.undertow.websockets.jsr.UndertowSession.close(UndertowSession.java:194) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
        at io.undertow.websockets.jsr.ServerWebSocketContainer.doClose(ServerWebSocketContainer.java:981) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
        at io.undertow.websockets.jsr.ServerWebSocketContainer.close(ServerWebSocketContainer.java:839) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
        at io.undertow.websockets.jsr.ServerWebSocketContainer.close(ServerWebSocketContainer.java:848) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
        at io.undertow.websockets.jsr.Bootstrap$WebSocketListener.contextDestroyed(Bootstrap.java:133) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
        at io.undertow.servlet.core.ApplicationListeners.contextDestroyed(ApplicationListeners.java:202) [undertow-servlet-2.0.32.Final.jar:2.0.32.Final]

    我明明用Set接口为啥是ArrayList接口,很奇怪,可能内部实现使用arraylist。不让删除的原因可能是并发 条件下时间复杂度比较高。(比较忙,没时间看代码解释)

    解决方案如下

        /**
         * 连接关闭调用的方法
         */
        @OnClose
        public void onClose() {
    
            for(final Iterator<Map.Entry<String, Set<WebSocketServer>>> iterator = clientRegistry.entrySet().iterator();
                iterator.hasNext();)
            {
                final Map.Entry<String, Set<WebSocketServer>> entry = iterator.next();
                final String dispatchNo = entry.getKey();
                final Set<WebSocketServer> clients = entry.getValue();
    
                for(WebSocketServer client: clients){
                    if (Objects.equals(client, this)) {
                        // 断开连接情况下,更新主板占用情况为释放
                        log.info("session:{}取消订阅工序派工单{}的产生数据", session.getId(), dispatchNo);
                        clients.remove(client);
                    } else {
    
                        final Session session = client.session;
                        final String sessionId = session.getId();
                        final boolean isAlive = session.isOpen();
                        if (!isAlive) {
                            clients.remove(client);
                            log.info("客户端会话@id:{}失效,已经踢出缓存",sessionId);
                        }
                    }
                }
            }
    
            final int clientCount = clientRegistry.values().stream().mapToInt(item -> item.size()).sum();
    
            if(clientCount > 0) {
                decreaseOnlineCount();
            }
        }

    websocket前后端长链接参考

    https://blog.csdn.net/qq_32330135/article/details/85282050 

    websocket客户端,在onmessage之外,也要定时发送心跳给服务器

  • 相关阅读:
    linux [Fedora] 下的 "飞秋/飞鸽传书"
    弹跳是不是自由落体?
    插件的简单原理
    WebService的简单应用
    普通按钮的另一种提交方式(调用后台事件)
    ASPNET服务端控件练习(一个机试题)
    AJAX简单的数据增删改与分页应用
    new XMLHttpRequest()和页面关系
    c++中placement new
    netty的引用计数
  • 原文地址:https://www.cnblogs.com/passedbylove/p/16027013.html
Copyright © 2020-2023  润新知