I、阻塞IO
来一个新连接,就新开一个线程处理,之后的操作全部由此线程来完成。
性能瓶颈:
1、每个线程都需要一部分内存,内存会被迅速消耗,同时,线程切换的开销非常大。
2、阻塞操作也是一个问题。首先,accept() 是一个阻塞操作,新建线程后不代表对方就将数据传输过来。所以,SocketChannel#read 方法将阻塞,等待数据,明显这个等待是不值得的。同理,write 方法也需要等待通道可写才能执行写入操作,这边的阻塞等待也是不值得的。
II、非阻塞IO
非阻塞 IO 的核心在于使用一个 Selector 来管理多个通道,可以是 SocketChannel,也可以是 ServerSocketChannel,将各个通道注册到 Selector 上,指定监听的事件。之后可以只用一个线程来轮询这个 Selector,看看上面是否有通道是准备好的,当通道准备好可读或可写,然后才去开始真正的读写,这样速度就很快了。
NIO 中 Selector 是对底层操作系统实现的一个抽象,管理通道状态其实都是底层系统实现的,这里简单介绍下在不同系统下的实现。
select:上世纪 80 年代就实现了,它支持注册 FD_SETSIZE(1024) 个 socket,在那个年代肯定是够用的,现在是不行了。
poll:1997 年,出现了 poll 作为 select 的替代者,最大的区别就是,poll 不再限制 socket 数量。
epoll:2002 年随 Linux 内核 2.5.44 发布,epoll 能直接返回具体的准备好的通道,时间复杂度 O(1)。
select 和 poll 都有一个共同的问题,那就是它们都只会告诉你有几个通道准备好了,但是不会告诉你具体是哪几个通道。所以,一旦知道有通道准备好以后,自己还是需要进行一次扫描,显然这个不太好,通道少的时候还行,一旦通道的数量是几十万个以上的时候,扫描一次的时间都很可观了,时间复杂度 O(n)。所以,后来才催生了epoll。
III、异步IO
Java 异步 IO 提供了两种使用方式:
返回 Future 实例
future.isDone();
判断操作是否已经完成,包括了正常完成、异常抛出、取消
future.cancel(true);
取消操作,方式是中断。参数 true 说的是,即使这个任务正在执行,也会进行中断。
future.isCancelled();
是否被取消,只有在任务正常结束之前被取消,这个方法才会返回 true
future.get();
获取执行结果,阻塞。
future.get(10, TimeUnit.SECONDS);
带超时时间的 get() 方法阻塞。
使用回调函数CompletionHandler
completed(V result, A attachment);
是否完成
failed(Throwable exc, A attachment);
是否失败
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。