zoukankan      html  css  js  c++  java
  • Java NIO开发需要注意的陷阱(转)

    陷阱1:处理事件忘记移除key
    在select返回值大于0的情况下,循环处理
    Selector.selectedKeys集合,每处理一个必须从Set中移除

    Iterator<SelectionKey> it=set.iterator();
        While(it.hasNext()){
        SelectionKey key=it.next();
        it.remove(); //切记移除
        „„处理事件
    }

     不移除的后果是本次的就绪的key集合下次会再次返回,导致无限循环,CPU消耗100%

     陷阱2:Selector返回的key集合非线程安全

    Selector.selectedKeys/keys 返回的集合都是非线程安全的
    Selector.selectedKeys返回的可移除
    Selector.keys 不可变
    对selected keys的处理必须单线程处理或者适当同步

    陷阱3:正确注册Channel和更新interest
    直接注册不可吗?
    channel.register(selector, ops, attachment);
    不是不可以,效率问题
    至少加两次锁,锁竞争激烈
    Channel本身的regLock,竞争几乎没有
    Selector内部的key集合,竞争激烈
    更好的方式:加入缓冲队列,等待注册,reactor单线程处理

    If(isReactorThread()){
        channel.register(selector,ops,attachment);
    }
    else{
        register.offer(newEvent(channel,ops,attachment));
        selector.wakeup();
    }

    同样,SelectionKey.interest(ops)
    在linux上会阻塞,需要获取selector内部锁做同步
    在win32上不会阻塞
    屏蔽平台差异,避免锁的激烈竞争,采用类似注册channel的方式:

    if (this.isReactorThread()) {
        key.interestOps(key.interestOps() | SelectionKey.OP_READ);
    } 
    else {
        this.register.offer(new Event(key,SelectionKey.OP_READ));
        selector.wakeup();
    }

    陷阱4:正确处理OP_WRITE
    OP_WRITE处理不当很容易导致CPU 100%
    OP_WRITE触发条件:
       前提:interest了OP_WRITE
       触发条件:
            socket发送缓冲区可写
            远端关闭
            有错误发生
    正确的处理方式:
       仅在已经连接的channel上注册
       仅在有数据可写的时候才注册
       触发之后立即取消注册,否则会继续触发导致循环
       处理完成后视情况决定是否继续注册
         没有完全写入,继续注册
         全部写入,无需注册

    陷阱5:正确取消注册channel
    SelectableChannel一旦注册将一直有效直到明确取消
    怎么取消注册?
       channel.close(),内部会调用key.cancel()
       key.cancel();
       中断channel的读写所在线程引起的channel关闭
    但是这样还不够!
       key.cancel()仅仅是将key加入cancelledKeys
       直到下一次select才真正处理
       并且channel的socketfd只有在真正取消注册后才会close(fd)

    后果是什么?
      服务端,问题不大,select调用频繁
      客户端,通常只有一个连接,关闭channel之后,没有调用select就关闭了selector
      sockfd没有关闭,停留在CLOSE_WAIT状态
    正确的处理方式,取消注册也应当作为事件交给reactor处理,及时wakeup做select
    适当的时候调用selector.selectNow()
      Netty在超过256连接关闭的时候主动调用一次selectNow

    static final int CLEANUP_INTERVAL=256;
    private boolean cleanUpCancelledKeys()throws IOException{
        if(cancelledKeys>=CLEANUP_INTERVAL){
            cancelledKeys=0;
            selector.selectNow();
            returntrue;
        }
        returnfalse;
    }
    //channel关闭的时候
    channel.socket.close();
    cancelledKeys++;

    陷阱6:同时注册OP_ACCPET和OP_READ,同时注册OP_CONNECT和OP_WRITE
    在底层来说,只有两种事件:read和write
    Java NIO还引入了OP_ACCEPT和OP_CONNECT
      OP_ACCEPT、OP_READ == Read
      OP_CONNECT、OP_WRITE == Write
    同时注册OP_ACCEPT和OP_READ ,或者同时注册OP_CONNECT和OP_WRITE在不同平台上产生错误的行为,避免这样做!

    陷阱7:正确处理connect
    SocketChannel.connect方法在非阻塞模式下可能返回false,切记判断返回值
        如果是loopback连接,可能直接返回true,表示连接成功
        返回false,后续处理
           注册channel到selector,监听OP_CONNECT事件
           在OP_CONNECT触发后,调用SocketChannel.finishConnect成功后,连接才真正建立
    陷阱:
        没有判断connect返回值
        没有调用finishConnect
        在OP_CONNECT触发后,没有移除OP_CONNECT,导致SelectionKey一直处于就绪状态,空耗CPU
           OP_CONNECT只能在还没有连接的channel上注册

    忠告

    尽量不要尝试实现自己的nio框架,除非有经验丰富的工程师
    尽量使用经过广泛实践的开源NIO框架Mina、Netty3、xSocket
    尽量使用最新稳定版JDK
    遇到问题的时候,也许你可以先看下java的bug database

  • 相关阅读:
    centos6升级内核
    centos7启动盘制作
    sed匹配字符串并将匹配行的后几行注释
    cmd下调用xshell登录linux服务器
    centos清除历史命令
    yum错误Cannot retrieve metalink for repository: epel/x86_6
    ansible-playbook
    jdk升级到1.8
    shell瞎记录
    shell 循环
  • 原文地址:https://www.cnblogs.com/pingh/p/3224990.html
Copyright © 2011-2022 走看看