美文网首页
Java NIO 遍历时为何要remove()?

Java NIO 遍历时为何要remove()?

作者: 我不吃甜食 | 来源:发表于2019-03-28 15:52 被阅读0次

    为何要remove()

    NIO模型如下

    while(selector.select()>0){
        Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
        while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                iterator.remove(); //为何要删除?
                if(selectionKey.isAcceptable()){
                     ....
                }else if(...){
                     ....
                }
        }
    }
    

    测试结果:一个客户端进行测试
    如果不删除,则下次select()就返回0,跳出循环 ;

    跟一下select()的源码,发现更新selectedKeys的操作在KQueueSelectorImpl.java的updateSelectedKeys()里 (mac下是KQueueSelectorImpl.java,linux的类不同,但逻辑基本相同)

     private int updateSelectedKeys(int entries)
            throws IOException
        {
            int numKeysUpdated = 0;
            boolean interrupted = false;
    
            // A file descriptor may be registered with kqueue with more than one
            // filter and so there may be more than one event for a fd. The update
            // count in the MapEntry tracks when the fd was last updated and this
            // ensures that the ready ops are updated rather than replaced by a
            // second or subsequent event.
            updateCount++;
    
            for (int i = 0; i < entries; i++) {
                int nextFD = kqueueWrapper.getDescriptor(i);
                if (nextFD == fd0) {
                    interrupted = true;
                } else {
                    MapEntry me = fdMap.get(Integer.valueOf(nextFD));
    
                    // entry is null in the case of an interrupt
                    if (me != null) {
                        int rOps = kqueueWrapper.getReventOps(i);
                        SelectionKeyImpl ski = me.ski;
                        if (selectedKeys.contains(ski)) {
                            // 不删除会走这里
                            if (me.updateCount != updateCount) {
                                if (ski.channel.translateAndSetReadyOps(rOps, ski)) {
                                    numKeysUpdated++;
                                    me.updateCount = updateCount;
                                }
                            } else {
                                // ready ops have already been set on this update
                                ski.channel.translateAndUpdateReadyOps(rOps, ski);
                            }
                        } else {
                            //删除会走到这里
                            ski.channel.translateAndSetReadyOps(rOps, ski);
                            if ((ski.nioReadyOps() & ski.nioInterestOps()) != 0) {
                                selectedKeys.add(ski);
                                numKeysUpdated++;
                                me.updateCount = updateCount;
                            }
                        }
                    }
                }
            }
    
            if (interrupted) {
                // Clear the wakeup pipe
                synchronized (interruptLock) {
                    IOUtil.drain(fd0);
                    interruptTriggered = false;
                }
            }
            return numKeysUpdated; //select()的返回值
        }
    

    从上面的代码可看出,最后的返回值是不是为0,与translateAndSetReadyOps()方法有关,这个方法又调用了translateReadyOps()

    public boolean translateReadyOps(int ops, int initialOps,
                                         SelectionKeyImpl sk) {
            int intOps = sk.nioInterestOps(); // Do this just once, it synchronizes
            int oldOps = sk.nioReadyOps();
            int newOps = initialOps;
    
            // ops为新到来的事件,可能包含多种事件
    
            if ((ops & PollArrayWrapper.POLLNVAL) != 0) {  
                return false;
            }
    
            if ((ops & (PollArrayWrapper.POLLERR
                        | PollArrayWrapper.POLLHUP)) != 0) {
                newOps = intOps;
                sk.nioReadyOps(newOps);
                // No need to poll again in checkConnect,
                // the error will be detected there
                readyToConnect = true;
                return (newOps & ~oldOps) != 0;
            }
            // 可读事件
            if (((ops & PollArrayWrapper.POLLIN) != 0) &&
                ((intOps & SelectionKey.OP_READ) != 0) &&
                (state == ST_CONNECTED))
                newOps |= SelectionKey.OP_READ;
            //连接事件
            if (((ops & PollArrayWrapper.POLLCONN) != 0) &&
                ((intOps & SelectionKey.OP_CONNECT) != 0) &&
                ((state == ST_UNCONNECTED) || (state == ST_PENDING))) {
                newOps |= SelectionKey.OP_CONNECT;
                readyToConnect = true;
            }
            //可写事件
            if (((ops & PollArrayWrapper.POLLOUT) != 0) &&
                ((intOps & SelectionKey.OP_WRITE) != 0) &&
                (state == ST_CONNECTED))
                newOps |= SelectionKey.OP_WRITE;
    
            // 设置准备好的事件
            sk.nioReadyOps(newOps);
    
            //这一步很关键
            return (newOps & ~oldOps) != 0;
        }
    

    最后一句是关键: 比如 newOps 设为了op_read, 而oldOps也是op_read,则会返回false; 最后一句的逻辑可用下面的表格描述

    老readyOps 新readyOps select() 返回值 描述
    write read > 0 事件完全改变
    read write and read > 0 事件增加
    write and read connect > 0 事件完全改变
    write and read read = 0 事件减少
    read read = 0 事件不变

    如果remove了,则可以认为老readyOps为0,此时只要有事件,最后一句就返回true,所以remove()不会丢失事件。

    结论

    不remove(),不一定有问题。但是remove()了,肯定不会出现问题。

    相关文章

      网友评论

          本文标题:Java NIO 遍历时为何要remove()?

          本文链接:https://www.haomeiwen.com/subject/efvqbqtx.html