xnio java_Java NIO开发需要注意的陷阱(转)

陷阱1:处理事件忘记移除key

在select返回值大于0的情况下,循环处理

Selector.selectedKeys集合,每处理一个必须从Set中移除

Iterator 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(newEvent(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()throwsIOException{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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值