博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java NIO(四)----阻塞IO与非阻塞IO
阅读量:3946 次
发布时间:2019-05-24

本文共 2965 字,大约阅读时间需要 9 分钟。

阻塞IO

  之间在中提到传统的IO是阻塞式的,而NIO是非阻塞式的(相对于网络通信而言)。通过下面图中可以了解IO阻塞的过程。

1.   客户端向服务端发起一个读写请求,但是服务端不确定数据是否有效,此时该线程就会进入阻塞状态,也就是说此线程在此期间无法做其他任何事情。

在这里插入图片描述

  1.   针对于上面的情况,后面有了一个治标不治本的方法-------使用多线程

使用多线程技术之后,1号线程阻塞了还有2号线程可用来做其他事,2号堵塞了还有3号线程可以用,相对于上面单线程来说确实能够解决一部分问题,但是线程池的线程数量总是有限制了,不可能无限使用。而且线程占用的都是实实在在的内存,开销非常大。所以使用多线程解决IO阻塞治标不治本。

在这里插入图片描述

非阻塞IO

  从前文中知道了阻塞式IO的弊端,再来了解一下非阻塞式IO的原理:

非阻塞式IO中有三个核心:
1.Channel 通道
2.Buffer 缓冲区
3.Selector 选择器
  Selector选择器在java API文档中被称作是多路复用器,用于注册一个或多个Channel通道,Selector会不断地检测通道状态是否是可读的或者可写的。它实现了集中管理监控通道状态,避免了因数据的不确定性导致的IO阻塞等待。(Selector的作用让我想到了Zookeeper注册中心)

在这里插入图片描述

  当我们的客户端向服务端请求一个数据的时候,Channel通道因为注册到了Selector选择器,所以Selector会等到服务端的数据完全准备就绪之后,再将此请求发送到服务器端的一个或者多个线程上去执行,因为数据此时是已经准备好了的,所以线程不会进入阻塞状态。

下面代码演示了客户端向服务端发送消息的实现过程;

@Test	void Client() throws IOException {
//1.获取通道 SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8083)); //2.切换成非阻塞模式 sChannel.configureBlocking(false); //3.分配指定大小的缓冲区 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //4.发送数据给服务器端 byteBuffer.put("客户端发送的数据".getBytes()); //5.切换为读模式 byteBuffer.flip(); sChannel.write(byteBuffer); byteBuffer.clear(); //6.关闭通道 sChannel.close(); } @Test void Server() throws IOException {
//1.获取通道 ServerSocketChannel serverChannel = ServerSocketChannel.open(); //2.切换为非阻塞模式 serverChannel.configureBlocking(false); //3.绑定连接 serverChannel.bind(new InetSocketAddress(8083)); //4.获取选择器 Selector selector = Selector.open(); //5.注册通道到选择器(注册的是监听事件) serverChannel.register(selector, SelectionKey.OP_ACCEPT); //6.通过选择器轮询式的获取选择器上已经准备就绪的事件 while (selector.select() > 0) {
//当选择器上准备就绪的时间大于0(至少有一个准备好了) //7.获取当前选择器中所有注册的选择键(已就绪的监听事件) Iterator
iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) {
//8.获取准备就绪的事件 SelectionKey next = iterator.next(); //9.判断该事件是什么时间(读、写、连接、接收) if (next.isAcceptable()) {
//10.若接收就绪,获取客户端连接 SocketChannel channel = serverChannel.accept(); //11.切换为非阻塞模式 channel.configureBlocking(false); //12.将该通道注册到选择器上 channel.register(selector, SelectionKey.OP_READ); } else if (next.isReadable()) {
//13.获取当前选择器上读就绪状态的通道 SocketChannel channel = (SocketChannel) next.channel(); //14.获取数据 ByteBuffer bBuffer = ByteBuffer.allocate(1024); int len = 0; while ((len = channel.read(bBuffer)) > 0) {
bBuffer.flip(); System.out.println(new String(bBuffer.array(),0,len)); bBuffer.clear(); } } //15.取消选择器 iterator.remove(); } } }

  上面代码中的第5步中的register方法是用于将Channel通道注册到Selector选择器上,其中的参数有:

1.Selector 选择器对象
2.SelectionKey 选择注册的事件
看一下register方法的源码:

在这里插入图片描述

其中第二个参数ops源码中注明了来源于SelectionKey类。该类中有以下4个常量:

1.表示读事件

public static final int OP_READ = 1 << 0;

2.表示写事件

public static final int OP_WRITE = 1 << 2;

3.表示连接事件

public static final int OP_CONNECT = 1 << 3;

4.表示接收事件

public static final int OP_ACCEPT = 1 << 4;

也就是说,register(Selector sel, int ops)方法中的第二个参数ops应该是来自于SelectionKey类中的4个常量中的其中一个,用来表示该通道在选择器上注册的是什么事件。

转载地址:http://fnhwi.baihongyu.com/

你可能感兴趣的文章
MTK编译笔记
查看>>
深入理解各种指针
查看>>
Android的SeekBar
查看>>
SMS 和 MMS 在输入字母的响应不一致
查看>>
如何判断手机是否处于漫游状态?
查看>>
恢复出厂设置时删除手机上所有联系人
查看>>
根据Sim卡的插卡情况过滤通话记录
查看>>
联系查看两张卡的未接电话记录
查看>>
把拒接电话作为已经接电话写到call log中
查看>>
FDN号码完全匹配
查看>>
Cosmos 拨号界面保存号码时先提示选择存储位置
查看>>
换卡或不插卡时删除通话记录
查看>>
静音模式下,来闹钟能响铃。
查看>>
调整提醒的优先级
查看>>
恢复出厂设置时清除闹钟
查看>>
如何添加一个提醒
查看>>
Cosmos 关机情况下来闹钟后增加是否开机选择功能
查看>>
日历的提醒内容可以根据需要修改
查看>>
如何使USSR编辑界面默认输入法为123
查看>>
手机中嵌入默认的快速拨号号码
查看>>