java实现非阻塞io原理_JAVA 非阻塞IO原理

本文介绍了Java中的非阻塞IO(NIO)原理,包括基本概念、NIO简介、非阻塞IO的工作机制以及Reactor模式。通过Reactor模式,Java NIO实现了事件驱动机制,提高网络服务的高伸缩性。文中还提供了Reactor模式的代码示例,展示了如何使用Selector和SelectionKey进行事件分发和处理。
摘要由CSDN通过智能技术生成

1.  基本概念

IO是主存和外部设备(硬盘、终端和网络等)传输数据的过程。IO是操作系统的底层功能实现,底层通过I/O指令进行完成。

2.nio简介 nio是java New IO的简称(并不只是指非阻塞IO),在jdk1.4里提供的新api。Sun官方标榜的特性如下:

–   为所有的原始类型提供(Buffer)缓存支持。

–   字符集编码解码解决方案。

–   Channel:一个新的原始I/O抽象。

–   支持锁和内存映射文件的文件访问接口。

–   提供多路(non-bloking)非阻塞式的高伸缩性网络I/O。

详细介绍可见 http://www.iteye.com/topic/834447

3.   非阻塞 IO

何为阻塞、何为非阻塞,非阻塞原理。

何为阻塞?

一个常见的网络IO通讯流程如下:

0818b9ca8b590ca3270a3433284dd417.png

何为非阻塞?

下面有个隐喻:

一辆从 A 开往 B 的公共汽车上,路上有很多点可能会有人下车。司机不知道哪些点会有哪些人会下车,对于需要下车的人,如何处理更好?

1. 司机过程中定时询问每个乘客是否到达目的地,若有人说到了,那么司机停车,乘客下车。 ( 类似阻塞式 )

2. 每个人告诉售票员自己的目的地,然后睡觉,司机只和售票员交互,到了某个点由售票员通知乘客下车。 ( 类似非阻塞 )

很显然,每个人要到达某个目的地可以认为是一个线程,司机可以认为是 CPU 。在阻塞式里面,每个线程需要不断的轮询,上下文切换,以达到找到目的地的结果。而在非阻塞方式里,每个乘客 ( 线程 ) 都在睡觉 ( 休眠 ) ,只在真正外部环境准备好了才唤醒,这样的唤醒肯定不会阻塞。

socket的操作都有一个共同的结构:

1. Read request

2. Decode request

3. Process service

4. Encode reply

5. Send reply

经典的网络服务的设计如下图,在每个线程中完成对数据的处理:

0818b9ca8b590ca3270a3433284dd417.png

但这种模式在用户负载增加时,性能将下降非常的快。我们需要重新寻找一个新的方案,保持数据处理的流畅,很显然,事件触发机制是最好的解决办法,当有事件发生时,会触动handler,然后开始数据的处理。 Reactor模式类似于AWT中的Event处理:

0818b9ca8b590ca3270a3433284dd417.png

Reactor模式参与者

1.Reactor 负责响应IO事件,一旦发生,广播发送给相应的Handler去处理,这类似于AWT的thread

2.Handler 是负责非堵塞行为,类似于AWT ActionListeners;同时负责将handlers与event事件绑定,类似于AWT addActionListener

非阻塞的原理

把整个过程切换成小的任务,通过任务间协作完成。

由一个专门的线程来处理所有的 IO 事件,并负责分发

事件驱动机制:事件到的时候触发,而不是同步的去监视事件。

线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的进程切换。

Reactor就是上面隐喻的售票员角色。每个线程的处理流程大概都是读取数据、解码、计算处理、编码、发送响应

如图:

0818b9ca8b590ca3270a3433284dd417.png

Java的NIO为reactor模式提供了实现的基础机制,它的Selector当发现某个channel有数据时,会通过SlectorKey来告知我们,在此我们实现事件和handler的绑定。

我们来看看Reactor模式代码:

public class Reactor implements Runnable{

final Selector selector;

final ServerSocketChannel serverSocket;

Reactor(int port) throws IOException {

selector = Selector.open();

serverSocket = ServerSocketChannel.open();

InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(),port);

serverSocket.socket().bind(address);

serverSocket.configureBlocking(false);

//向selector注册该channel

SelectionKey sk =serverSocket.register(selector,SelectionKey.OP_ACCEPT);

logger.debug("-->Start serverSocket.register!");

//利用sk的attache功能绑定Acceptor 如果有事情,触发Acceptor

sk.attach(new Acceptor());

logger.debug("-->attach(new Acceptor()!");

}

public void run() { // normally in a new Thread

try {

while (!Thread.interrupted())

{

selector.select();

Set selected = selector.selectedKeys();

Iterator it = selected.iterator();

//Selector如果发现channel有OP_ACCEPT或READ事件发生,下列遍历就会进行。

while (it.hasNext())

//来一个事件 第一次触发一个accepter线程

//以后触发SocketReadHandler

dispatch((SelectionKey)(it.next()));

selected.clear();

}

}catch (IOException ex) {

logger.debug("reactor stop!"+ex);

}

}

//运行Acceptor或SocketReadHandler

void dispatch(SelectionKey k) {

Runnable r = (Runnable)(k.attachment());

if (r != null){

// r.run();

}

}

class Acceptor implements Runnable { // inner

public void run() {

try {

logger.debug("-->ready for accept!");

SocketChannel c = serverSocket.accept();

if (c != null)

//调用Handler来处理channel

new SocketReadHandler(selector, c);

}

catch(IOException ex) {

logger.debug("accept stop!"+ex);

}

}

}

}

以上代码中巧妙使用了SocketChannel的attach功能,将Hanlder和可能会发生事件的channel链接在一起,当发生事件时,可以立即触发相应链接的Handler。

将数据读出后,可以将这些数据处理线程做成一个线程池,这样,数据读出后,立即扔到线程池中,这样加速处理速度:

0818b9ca8b590ca3270a3433284dd417.png

可参考:

http://www.jdon.com/concurrent/reactor.htm

http://javag.iteye.com/blog/221641

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值