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

  • 相关阅读:
    11111 Generalized Matrioshkas
    Uva 442 Matrix Chain Multiplication
    Uva 10815 Andy's First Dictionary
    Uva 537 Artificial Intelligence?
    Uva 340 MasterMind Hints
    SCAU 9508 诸葛给我牌(水泥题)
    Uva 10420 List of Conquests(排序水题)
    Uva 409 Excuses, Excuses!
    10/26
    11/2
  • 原文地址:https://www.cnblogs.com/jpfss/p/10197122.html
Copyright © 2011-2022 走看看