非阻塞的java nio

JAVA NIO

Java Nio是对java io的改进,它支持阻塞和非阻塞两种方式,如果选择阻塞方式就退化到java io.

在java io处理client和server端的连接中,一个主线程接受连接请求,线程池中有多个工作线程处理具体连接和I/O操作,优点是能够响应多个client的响应需求,达到并发的目的,但是有以下局限,

⑴ Java 虚拟机会为每个线程分配独立的堆栈空间, 工作线程数目越多, 系统开销就越大, 而且增加了 Java虚拟机调度线程的负担, 增加了线程之间同步的复杂性, 提高了线程死锁的可能性;

⑵ 工作线程的许多时间都浪费在阻塞 I/O 操作上, Java 虚拟机需要频繁地转让 CPU 的使用权, 使进入阻塞状态的线程放弃CPU, 再把CPU 分配给处于可运行状态的线程.

由此可见, 工作线程并不是越多越好. 保持适量的工作线程, 会提高服务器的并发性能, 但是当工作线程的数目达到某个极限, 超出了系统的负荷时, 反而会减低并发性能, 使得多数客户无法快速得到服务器的响应,并且可能server本身有很多线程要跑,也会影响了server的其他处理性能.

为此在JDK1.4以后,引入了非阻塞的java nio机制,在该机制中,只需要一个或者少量线程就可以控制多个连接,

为此,它必须解决以下问题:

(1) 一个线程怎么管理多个线程?

(2) 这个线程怎么知道要管理哪些连接?

(3) 这个线程怎么知道哪些连接现在需要处理,需要怎么处理?

首先,java nio引入了channel的概念。 channel的两端,服务端是ServerSocketChannel,相当于java io的ServerSocket,客户端是SocketChannel,相当于java io的Socket,所以channel就相当于java io的套接字socket连接。将对channel的操作分成read, write, accept(server端),connect几种操作类型;

其次,选择一个管理者--Selector对象,由该对象通过SelectionKey 对象来管理多个channel。 把需要该Selector对象管理的channel,以及channel的操作类型调用register()方法注册到该Selector对象中,(一个选择器最多可以同时被63个通道一起注册使用。)register方法会生成一个SelectionKey对象,该对象是追踪注册事件的句柄,在SelectionKey 对象的有效期间, Selector对象会一直监控与 SelectionKey 对象相关的事件, 如果事件发生, 就会把 SelectionKey 对象加入到该Selector对象的selected-keys 集合中,通过这些key,我们就可以找到刚刚注册的channel,进而进行相应的操作。 Selcetor对象对channel的监控是通过Selector不停的轮询各个已经注册事件的channel,一旦轮询到一个channel有所注册过的事件发生,比如,connect就绪,则返回。Selector有三种方式,selectNow()、select()、select(long waitTime)进行轮询, 其中selectNow()是轮询后立即返回;select()是轮询后没有就绪事件或者挂起错误事件,变为阻塞状态,一直等到有事件产生;select(long waitTime)是轮询后没有就绪事件或者挂起错误事件,就处于阻塞waitTime这么长时间,再返回。 在阻塞的过程中,如果该Selector管理下的任意一个通道有就绪或者挂起错误事件产生,该方法也返回;或者调用该Selector的 wakeUp()、close()、或者当前的线程已经中断,select方法也立即返回。

SelectionKey有四种事件,这四种事件都是int类型的常量,可以通过“|”等运算符进行组装。

SelectionKey.OP_ACCEPT: 假定在选择操作开始时,选择键的 interest 集合中已包含 OP_ACCEPT。如果选择器检测到相应的服务器套接字通道已为接受另一个连接而准备就绪,或者有一个挂起的错误,那么它会向该键的 ready 集合中添加 OP_ACCEPT,并将该键添加到已选择键集中;

SelectionKey.OP_CONNECT: 假定在选择操作开始时,选择键的 interest 集合中已包含 OP_CONNECT。如果选择器检测到相应的套接字通道已为完成其连接序列而准备就绪,或者有一个挂起的错误,那么它会向该键的 ready 集合中添加 OP_CONNECT,并将该键添加到已选择键集中;

SelectionKey.OP_READ: 假定在选择操作开始时,选择键的 interest 集合中已包含 OP_READ。如果选择器检测到相应的通道已为读取准备就绪、已经到达流的末尾、已经被远程关闭而无法进行进一步的读取,或者有一个挂起的错误,那么它会向该键的 ready 集合中添加 OP_READ,并将该键添加到已选择键集中;

SelectionKey.OP_WRITE: 假定在选择操作开始时,选择键的 interest 集合中已包含 OP_WRITE。如果选择器检测到相应的通道已为写入准备就绪、已经被远程关闭而无法进行进一步的写入,或者有一个挂起的错误,那么它会向该键的 ready 集合中添加 OP_WRITE,并将该键添加到已选择键集中。

Connect()是异步的方式,即会立即返回而继续执行后面的代码,可以在自己的系统中封装成同步方式,通过wait()方法。如果此通道处于非阻塞模式,则调用此方法会发起一个非阻塞连接操作。如果立即建立连接(使用本地连接时就是如此),则此方法返回 true。否则此方法返回 false,并且必须在以后通过调用 finishConnect 方法来完成该连接操作。

在服务器端,ServerSocketChannel通过静态函数open()返回一个实例serverChl。然后该通道调用serverChl.socket().bind()绑定到服务器某端口,并调用register(Selector sel, SelectionKey.OP_ACCEPT)注册OP_ACCEPT事件到一个选择器中(ServerSocketChannel只可以注册OP_ACCEPT事件)。当有客户请求连接时,选择器就会通知该通道有客户连接请求,就可以进行相应的输入输出控制了;在客户端,clientChl实例注册自己感兴趣的事件后(可以是OP_CONNECT,OP_READ,OP_WRITE的组合),调用clientChl.connect (InetSocketAddress )连接服务器然后进行相应处理。注意,这里的连接是异步的,即会立即返回而继续执行后面的代码。

Java的一些方法:

Selector 类的主要方法如下:

• public static Selector open() throws IOException

这是 Selector 的静态工厂方法, 创建一个 Selector 对象.

•public boolean isOpen()

判断 Selector 是否处于打开状态. Selector 对象创建后就处于打开状态, 当调用那个了 Selector 对象的 close() 方法, 它就进入关闭状态.

• public Selector wakeup()

呼醒执行 Selector 的 select() 方法(也同样设用于 select(long timeout) 方法) 的线程. 当线程A 执行 Selector 对象的 wakeup() 方法时, 如果线程B 正在执行同一个 Selector 对象的 select() 方法, 或者线程B 过一会儿会执行这个 Selector 对象的 select() 方法, 那么线程B 在执行 select() 方法时, 会立即从 select() 方法中返回, 而不会阻塞. 假如, 线程B 已经在 select() 方法中阻塞了, 也会立即被呼醒, 从select() 方法中返回. wakeup() 方法只能呼醒执行select() 方法的线程B 一次. 如果线程B 在执行 select() 方法时被呼醒后, 以后在执行 select() 方法, 则仍旧按照阻塞方式工作, 除非线程A 再次调用 Selector 对象的 wakeup() 方法.

参考网址:

http://www.cnblogs.com/michelleAnn2011/archive/2012/04/01/2427484.html

http://blog.csdn.net/xhh198781/article/details/6635775

http://www.cnblogs.com/balaamwe/archive/2011/10/24/2222197.html

追加:

nio中取得事件通知,就是在selector的select事件中完成的,在selector事件时有一个线程,这个线程具体的处理简单点说就是:向操作系统询问,selector中注册的Channel&&SelectionKey的偶对各种事件是否有发生,如果有则添加到selector的selectedKeys属性Set中去,并返回本次有多少个感兴趣的事情发生。程序员发现这个值>0,表示有事件发生,马上迭代selectedKeys中的SelectionKey,根据Key中的表示的事件,来做相应的处理。

实际上,这段说明表明了异步socket的核心,即异步socket不过是将多个socket的调度(或者还有他们的线程调度)全部交给操作系统自己去完成,异步的核心Selector,不过是将这些调度收集、分发而已。因为操作系统的socket、线程调度再咋D也比你JVM中要强,效率也高。

而且就算jvm做的和操作系统一样好,性能一样高(当然这是不现实的),使用异步socket你至少也节约了一半的系统消耗,想想假定操作系统本身也是使用线程来维护N个socket连接,在传统的java编程中,你还必须为这些socket还多起一个java线程,那至少是2N个线程,现在只需要N+1。在高并发的情况下,你自己去想吧。

http://www.cnblogs.com/feidao/archive/2005/07/15/193788.html