陷阱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

Java NIO开发需要注意的陷阱(转)的更多相关文章

  1. Netty精粹之JAVA NIO开发需要知道的

    学习Netty框架以及相关源码也有一小段时间了,恰逢今天除夕,写篇文章总结一下.Netty是个高效的JAVA NIO框架,总体框架基于异步非阻塞的设计,基于网络IO事件驱动,主要贡献在于可以让用户基于 ...

  2. Java多线程:Linux多路复用,Java NIO与Netty简述

    JVM的多路复用器实现原理 Linux 2.5以前:select/poll Linux 2.6以后: epoll Windows: IOCP Free BSD, OS X: kqueue 下面仅讲解L ...

  3. Java NIO服务器端开发

    一.NIO类库简介 1.缓冲区Buffer Buffer是一个对象,包含一些要写入和读出的数据. 在NIO中,所有的数据都是用缓冲区处理的,读取数据时,它是从通道(Channel)直接读到缓冲区中,在 ...

  4. Eclipse下Android开发错误之Unable to execute dex: java.nio.BufferOverflowException. Check the Eclipse log for stack trace

    升级了Android版本后,在运行应用时提示: [2013-11-27 10:37:35 - Dex Loader] Unable to execute dex: java.nio.BufferOve ...

  5. 我的Java开发学习之旅------>Java NIO 报java.nio.charset.MalformedInputException: Input length = 1异常

    今天在使用Java NIO的Channel和Buffer进行文件操作时候,报了java.nio.charset.MalformedInputException: Input length = 1异常, ...

  6. 5种调优Java NIO和NIO.2的方式

    Java NIO(New Input/Output)——新的输入/输出API包——是2002年引入到J2SE 1.4里的.Java NIO的目标是提高Java平台上的I/O密集型任务的性能.过了十年, ...

  7. Java NIO浅析 转至 美团技术团队

    出处: Java NIO浅析 NIO(Non-blocking I/O,在Java领域,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服 ...

  8. Netty快速入门(05)Java NIO 介绍-Selector

    Java NIO Selector Selector是Java NIO中的一个组件,用于检查一个或多个NIO Channel的状态是否处于可读.可写.如此可以实现单线程管理多个channels,也就是 ...

  9. Java NIO使用及原理分析(1-4)(转)

    转载的原文章也找不到!从以下博客中找到http://blog.csdn.net/wuxianglong/article/details/6604817 转载自:李会军•宁静致远 最近由于工作关系要做一 ...

随机推荐

  1. 优秀前端工程师必备: 我要一个新窗口: js开新窗的2种姿势

    1.<a href="https://www.cnblogs.com/" title="博客园">当前页面打开博客园</a> js代码等 ...

  2. linux每天一小步---cat命令详解

    1 命令功能 cat命令是linux系统下的一个文本输出命令,通常用于查看文件的内容. 2 命令语法 cat  [选项参数]  文件名 3 命令参数 -n  由1开始对所有输出的内容行数编号 -b   ...

  3. Zynq学习笔记(1)——Hellow World

    Zynq是一款SOC芯片,之前只是用了PL(Programmable Logic)部分,而Zynq最突出的功能,就是内部的双核Cortex-A9,所以从现在开始我将学习ZYNQ的SOC学习(PS部分) ...

  4. CURL命令测试网站打开速度

    curl -o /dev/null -s -w %{time_namelookup}:%{time_connect}:%{time_starttransfer}:%{time_total} http: ...

  5. 关于fastjson的一个坑:输出json时,bean对象属性首字母默认被小写

    fastjson 是一个性能很好的 Java 语言实现的 JSON 解析器和生成器,来自阿里巴巴. 主要特点: 快速FAST: 比其它任何基于Java的解析器和生成器更快,包括jackson 强大:支 ...

  6. 获取web项目中的webroot目录路径

    备忘,一段代码: @Override public void init(FilterConfig arg0) throws ServletException { // TODO Auto-genera ...

  7. ajax +LoadLayer插件实现访问页面跳转loading..

    布局页:第一步进行扩展ajax$(function () { $.ajax2 = function (options) {//遮罩 Mask();//jquery 原生ajax $.ajax(opti ...

  8. 1-初步了解C#-语言基础

    本篇博客对应视频讲解 前言 终于开始讲语言了,我选择讲C#.为什么呢?因为C#是很好的入门语言,简洁.全面,面向对象类型安全,开发体验好,上手容易.而其他的语言也已经有人讲了很多了,C#相对来说要少一 ...

  9. MyBatis高级及其SSM框架搭建

    代码生成器 首先meaven项目中导入支持包 <dependencies> <!-- https://mvnrepository.com/artifact/mysql/mysql-c ...

  10. Visual Studio code安装步骤

    1.官方下载:https://code.visualstudio.com/,本人电脑是window系统  下载之后,双击安装,安装完之后左侧栏那边是英文,如何变为中文: 按快捷键ctrl+shift+ ...