• 微信公众号:美女很有趣。 工作之余,放松一下,关注即送10G+美女照片!

关于 IO NIO AIO的碎片知识

开发技术 开发技术 3小时前 2次浏览

三种IO 主要区别是 同步、异步 非阻塞/阻塞

同步 异步 : 任务序列直接是否依赖

阻塞 非阻塞: cpu的消耗维度来讲的 缺点增加了线程切换

网络I/O是长连接 ,传输数据不多的情况下,提升性能有效
但是会增加cpu消耗 要考虑系统的瓶颈是在I/O还是在cpu
异步阻塞:分布式数据库 :通常会有一条同步阻塞的记录,还有3-4条备份记录到其他机器
异步非阻塞:集群直接的消息通信 一般用这种I/O组合方式

标准的TO 基于。字节流与字符流进行操作,阻塞IO
NIO 基于通道channel和缓存区Buffer,支持非阻塞Io,提供选择器selector 简单说是多路复用。

selector可用来在线程中关联多个通道,并进行事件监听。
关于 IO NIO AIO的碎片知识

1、NIO 是利用了单线程轮询事件的机制,通过高效地定位就绪的 Channel,来决定做什么,仅仅 select 阶段是阻塞的,可以有效避免大量客户端连接时,频繁线程切换带来的问题,应用的扩展能力有了非常大的提高。
这个过程是阻塞的

原理:创建NIO服务端的主要步骤如下:

打开ServerSocketChannel,监听客户端连接
绑定监听端口,设置连接为非阻塞模式
创建Reactor线程,创建多路复用器并启动线程
将ServerSocketChannel注册到Reactor线程中的Selector上,监听ACCEPT事件
Selector轮询准备就绪的key
Selector监听到新的客户端接入,处理新的接入请求,完成TCP三次握手,遍历物理链路
设置客户端链路为非阻塞模式
将新接入的客户端连接注册到Reactor线程的Selector上,监听读操作,读取客户端发送的网络消息
异步读取客户端消息到缓冲区
对Buffer编解码,处理半包消息,将解码成功的消息封装成Task
将应答消息编码为Buffer,调用SocketChannel的write将消息异步发送给客户端
所以不能保证一次能把需要发送的数据发送完,此时就会出现写半包的问题。我们需要注册写操作,不断轮询Selector将没有发送完的消息发送完毕,然后通过Buffer的hasRemain()方法判断消息是否发送完成。

Q: 在NIO中Selector的好处是什么?
A:

可以用更少的线程来管理各个通道。
减少线程上下文切换的资源开销。

Q: Selector注册时,支持监听哪几种事件,对应的常量是什么?
A:共有4种可监听事件

Connect 成功连接到1个服务器,对应常量SelectionKey.OP_CONNECT
Accept 准备好接收新进入的连接, 对应常量SelectionKey.OP_ACCEPT
Read, 有数据可读,对应常量SelectionKey.OP_READ
Write 接收到往里写的数据, 对应常量SelectionKey.OP_WRITE
SelectionKey键表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系

Q:何使用selector对准备就绪的通道做处理:

A:调用select()方法获取已就绪的通道,返回的int值表示有多少通道已经就绪
从selector中获取selectedkeys
遍历selectedkeys
查看各SelectionKey中 是否有事件就绪了。
如果有事件就绪,从key中获取对应对应管道。做对应处理
栗子:
while(true) {
int readyNum = selector.select();
if (readyNum == 0) {
continue;
}

Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();

while(it.hasNext()) {
SelectionKey key = it.next();
if(key.isAcceptable()) {
// 接受连接
} else if (key.isReadable()) {
// 通道可读
} else if (key.isWritable()) {
// 通道可写
}

it.remove();
}
}

Q:之前说NIO是非阻塞IO,但为什么上面却说select()方法是阻塞的?

A:其实NIO的非阻塞,指的是IO不阻塞,即我们不会卡在read()处,我们会用selector去查询就绪状态,如果状态ok就。
而查询操作是需要时间,因此select()必须要把所有通道都检查一遍才能告诉结果,因此select这个查询操作是阻塞的。

Q:select()方法其实是阻塞方法,即调用时会进入等待,直到把所有通道都轮询完毕。如果希望提前结束select(),有哪些方法?
A:有2个办法:
wakeup(), 调用后,select()方法立刻返回。
close(), 直接关闭selector。

Q: 多线程读写同一文件时,如何加锁保证线程安全?
A:使用FileChannel的加锁功能。

RandomAccessFile randFile = new RandomAccessFile(target, “rw”);
FileChannel channel = randFile.getChannel();
// pos和siz决定加锁区域, shared指定是否是共享锁
FileLock fileLock = channel.lock(pos , size , shared);
if (fileLock!=null) {
do();
// 这里简化了,实际上应该用try-catch
fileLock.release();
}

Q: 如果需要读1个特大文件,可以使用什么缓冲区?
A:使用MappedByteBuffer。
这个缓冲区可以把大文件理解成1个byte数组来访问(但实际上并没有加载这么大的byte数组,实际内容放在内存+虚存中)。
主要通过FileChannel.map(模式,起始位置,区域)来生成1个MappedByteBuffer。然后可以用put和get去处理对应位置的byte。

int length = 0x8FFFFFF;//一个byte占1B,所以共向文件中存128M的数据
try (FileChannel channel = FileChannel.open(Paths.get(“src/c.txt”),
StandardOpenOption.READ, StandardOpenOption.WRITE);) {
MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, length);
for(int i=0;i<length;i++) {
mapBuffer.put((byte)0);
}
for(int i = length/2;i<length/2+4;i++) {
//像数组一样访问
System.out.println(mapBuffer.get(i));
}
}

MappedByteBuffer 的三种模式:

MapMode.READ_ONLY(只读): 试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException。
MapMode.READ_WRITE(读/写): 对得到的缓冲区的更改会写入文件,需要调用fore()方法
MapMode.PRIVATE(专用): 可读可写,但是修改的内容不会写入文件,只是buffer自身的改变。


程序员灯塔
转载请注明原文链接:关于 IO NIO AIO的碎片知识
喜欢 (0)