一个故事讲清楚NIO

博客以银行工作流程为例,对比了BIO、NIO和异步三种工作方式对效率的影响。BIO方式下职员处理完整流程,效率较低;NIO方式拆分任务,效率提升;异步方式进一步优化,吞吐量大幅提高。NIO+异步适用于多场景,但可能增加请求等待时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

阿里云幸运券

作者:zhanlijun

假设某银行只有10个职员。该银行的业务流程分为以下4个步骤:

1) 顾客填申请表(5分钟);

2) 职员审核(1分钟);

3) 职员叫保安去金库取钱(3分钟);

4) 职员打印票据,并将钱和票据返回给顾客(1分钟)。

我们看看银行不同的工作方式对其工作效率到底有何影响。

一、BIO方式
每来一个顾客,马上由一位职员来接待处理,并且这个职员需要负责以上4个完整流程。当超过10个顾客时,剩余的顾客需要排队等候。

我们算算这个银行一个小时到底能处理多少顾客?一个职员处理一个顾客需要10分钟(5+1+3+1)时间,一个小时(60分钟)能处理6个顾客,一共10个职员,那就是只能处理60个顾客。

可以看到银行职员的工作状态并不饱和,比如在第1步,其实是处于等待中。

这种工作其实就是BIO,每次来一个请求(顾客),就分配到线程池中由一个线程(职员)处理,如果超出了线程池的最大上限(10个),就扔到队列等待 。

二、NIO方式
如何提高银行的吞吐量呢?

思路:分而治之,将任务拆分开来,由专门的人负责专门的任务。

具体来讲,银行专门指派一名职员A,A的工作就是每当有顾客到银行,他就递上表格让顾客填写,每当有顾客填好表后,A就将其随机指派给剩余的9名职员完成后续步骤。

我们计算下这种工作方式下银行一个小时到底能处理多少顾客?

假设顾客非常多,职员A的工作处于饱和中,他不断的将填好表的顾客带到柜台处理,柜台一个职员5分钟能处理完一个顾客,一个小时9名职员能处理:9*(60/5)=108。

可见工作方式的转变能带来效率的极大提升。

这种工作方式其实就是NIO的思路。下图是非常经典的NIO说明图,mainReactor线程负责监听server socket,accept新连接,并将建立的socket分派给subReactor;subReactor可以是一个线程,也可以是线程池(一般可以设置为CPU核数),负责多路分离已连接的socket,读写网络数据,这里的读写网络数据可类比顾客填表这一耗时动作,对具体的业务处理功能,其扔给worker线程池完成。

可以看到典型NIO有三类线程,分别是mainReactor线程、subReactor线程、work线程。不同的线程干专业的事情,最终每个线程都没空着,系统的吞吐量自然就上去了。

三、异步方式
第二种工作方式有没有什么可以提高的地方呢?

仔细查看可发现第3步骤这3分钟柜台职员是在等待中度过的,那怎么能让柜台职员保持满负荷呢?

还是分而治之的思路,指派1个职员B来专门负责第3步骤。每当柜台员工完成第2步时,就通知职员B来负责与保安沟通取钱。这时候柜台员工可以继续处理下一个顾客。当职员B拿到钱之后,他会怎么办呢?他会通知顾客钱已经到柜台了,让顾客重新排队处理,当柜台职员再次服务该顾客时,发现该顾客前3步已经完成,直接执行第4步即可。

我们可以算算通过这种方法,银行的吞吐量能提高到多少。

假设职员B的工作非常饱和,柜台一个职员现在2分钟能处理完一个顾客,一个小时8名职员能处理:8*(60/2)=240。

在当今web服务中,经常需要通过RPC或者Http等方式调用第三方服务,这里对应的就是第3步,如果这步耗时较长,通过异步方式将能极大降低资源使用率。

jetty Continuations 就实现了上述异步方式,有兴趣的同学可以去尝试下(http://wiki.eclipse.org/Jetty/Feature/Continuations)。

NIO+异步的方式能让少量的线程(资源)做大量的事情,这适用于很多应用场景,比如代理服务、api服务、长连接服务等等,这些应用如果用同步方式将耗费大量机器资源。尽管NIO+异步能提高系统吞吐量,但其并不能让一个请求的等待时间下降,相反可能会增加等待时间。

四、小结
总结就一句:“分而治之,将任务拆分开来,由专门的人负责专门的任务”,这不仅在计算机领域生效,在整个社会领域都生效。

腾讯云代金券

原文链接

http://www.cnblogs.com/LBSer/p/4622749.html

服务推荐

<think>嗯,用户问的是NIO中的Selector是怎么选择的,我需要详细解释清楚Selector的工作原理。首先,得回顾一下Java NIO的基本概念,Selector是核心组件之一,用于处理多个通道的I/O事件。可能需要先解释Selector的作用,比如多路复用的机制,这样用户能明白为什么需要它。 然后得说明Selector如何工作,比如通过注册Channel并监听感兴趣的事件。需要提到Selector的三个关键方法:select()、selectNow()和select(long timeout),这几个的区别要讲清楚,比如阻塞、非阻塞和超时阻塞的情况。 还要涉及到SelectionKey,每个注册的通道都会有一个对应的SelectionKey,里面保存了事件类型和通道的信息。事件类型包括OP_READ、OP_WRITE、OP_CONNECT、OP_ACCEPT,这些需要分别解释,用户可能对每种事件的应用场景不太清楚。 可能还需要提到就绪集合(selected key set)和已注册集合(registered key set)的区别,Selector维护这些集合来处理事件。处理完事件后,必须手动移除SelectionKey,否则下次还会处理,这是一个容易出错的地方,应该提醒用户注意。 另外,Selector在多线程环境下的使用需要注意,通常一个Selector由一个线程处理,避免并发问题。可能需要提到Reactor模式,这样用户能理解实际应用中的设计模式。 还有,要举一个简单的例子,比如服务器端如何注册ServerSocketChannel到Selector,监听ACCEPT事件,然后处理连接后的读写事件。代码示例能帮助用户更直观地理解,但用户可能只需要文字描述,所以可以简要说步骤。 最后,可能还要比较一下Selector和传统阻塞I/O的区别,强调其高效处理大量连接的优势,以及适用场景,比如高并发服务器应用。但用户的问题主要集中在Selector的选择机制上,所以这部分可以简要带过。 需要检查是否有遗漏的关键点,比如wakeup()方法的作用,或者如何处理关闭的通道,确保覆盖Selector使用中的常见问题和最佳实践。语言要尽量通俗,避免过于技术化,让不同层次的用户都能理解。结构要清晰,分步骤或分点解释,保持逻辑连贯。</think>在Java NIO中,`Selector`是实现**I/O多路复用**的核心组件。它的工作原理可以用以下步骤解释: --- ### 一、核心概念 1. **Selector(选择器)** - 一个`Selector`可以同时监控多个`Channel`的I/O事件 - 通过单线程管理多个通道,实现高效I/O处理 2. **Channel(通道)** - 必须处于非阻塞模式才能注册到Selector - 支持的类型:`SocketChannel`、`ServerSocketChannel`等 3. **SelectionKey(选择键)** - 通道注册到选择器时返回的"票据" - 保存了通道与选择器的关联关系及感兴趣的事件类型 --- ### 二、选择过程的关键步骤 1. **注册通道** ```java channel.configureBlocking(false); // 必须非阻塞 SelectionKey key = channel.register(selector, SelectionKey.OP_READ); ``` 2. **选择就绪通道** ```java int readyChannels = selector.select(); // 阻塞直到有事件就绪 // 或者使用: // selector.selectNow(); // 非阻塞 // selector.select(1000); // 带超时 ``` 3. **处理就绪事件** ```java Set<SelectionKey> selectedKeys = selector.selectedKeys(); for (SelectionKey key : selectedKeys) { if (key.isAcceptable()) { /* 处理新连接 */ } if (key.isReadable()) { /* 处理读事件 */ } if (key.isWritable()) { /* 处理写事件 */ } } selectedKeys.clear(); // 必须清空已处理的事件 ``` --- ### 三、事件类型详解 | 事件常量 | 触发条件 | 适用场景 | |----------------------|-----------------------------------|-------------------------| | `SelectionKey.OP_ACCEPT` | 服务器准备好接受新连接 | ServerSocketChannel | | `SelectionKey.OP_CONNECT` | 客户端成功连接到服务器 | SocketChannel | | `SelectionKey.OP_READ` | 数据可读 | 所有可读场景 | | `SelectionKey.OP_WRITE` | 通道准备好写入数据 | 需要发送大量数据时 | --- ### 四、底层实现原理 1. **操作系统级支持** - 在Linux上基于`epoll`实现 - Windows使用`IOCP`(完成端口) - 不同系统的实现细节被JVM屏蔽 2. **就绪集合维护** ```mermaid graph LR A[已注册的Channel] -->|注册时指定兴趣集| B(Selector维护的注册集合) B -->|系统通知就绪事件| C(Selector的就绪集合) C --> D[应用程序遍历处理] ``` --- ### 五、最佳实践建议 1. **合理设置兴趣集** - 仅在需要写操作时才注册`OP_WRITE` - 处理完事件后及时修改兴趣集 2. **线程安全注意事项** - `Selector`本身线程不安全 - 推荐单线程处理选择逻辑(Reactor模式) 3. **性能优化技巧** - 使用`selector.wakeup()`唤醒阻塞的select() - 及时关闭不再使用的Channel和SelectionKey --- ### 六、与传统阻塞IO对比 ```text +-------------------+---------------------+-----------------------+ | 特性 | 阻塞IO | NIO+Selector | +-------------------+---------------------+-----------------------+ | 线程使用 | 每个连接一个线程 | 单线程处理多个连接 | | CPU利用率 | 上下文切换开销大 | 更高效的CPU使用 | | 适用场景 | 低并发连接 | 高并发连接(如IM服务) | | 编程复杂度 | 简单 | 较复杂 | +-------------------+---------------------+-----------------------+ ``` 理解Selector的机制是构建高性能网络应用的基础,实际开发中通常结合线程池等模式来充分发挥其优势。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值