阿里面试题BIO和NIO数量问题附答案和代码
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了阿里面试题BIO和NIO数量问题附答案和代码相关的知识,希望对你有一定的参考价值。
一、问题
BIO 和 NIO 作为 Server 端,当建立了 10 个连接时,分别产生多少个线程?
答案: 因为传统的 IO 也就是 BIO 是同步线程堵塞的,所以每个连接都要分配一个专用线程来处理请求,这样 10 个连接就会创建 10 个线程去处理。而 NIO 是一种同步非阻塞的 I/O 模型,它的核心技术是多路复用,可以使用一个链接上的不同通道来处理不同的请求,所以即使有 10 个连接,对于 NIO 来说,开启 1 个线程就够了。
二、BIO 代码实现
public
class
DemoServer
extends
Thread
{
private
ServerSocket
serverSocket
;
public
int
getPort
()
{
return
serverSocket
.
getLocalPort
();
}
public
void
run
()
{
try
{
serverSocket
=
new
ServerSocket
(
0
);
while
(
true
)
{
Socket
socket
=
serverSocket
.
accept
();
RequestHandler
requestHandler
=
new
RequestHandler
(
socket
);
requestHandler
.
start
();
}
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
finally
{
if
(
serverSocket
!=
null
)
{
try
{
serverSocket
.
close
();
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
}
}
}
public
static
void
main
(
String
[]
args
)
throws
IOException
{
DemoServer
server
=
new
DemoServer
();
server
.
start
();
try
(
Socket
client
=
new
Socket
(
InetAddress
.
getLocalHost
(),
server
.
getPort
()))
{
BufferedReader
bufferedReader
=
new
BufferedReader
(
new
InputStreamReader
(
client
.
getInputStream
()));
bufferedReader
.
lines
().
forEach
(
s
->
System
.
out
.
println
(
s
));
}
}
}
// 简化实现,不做读取,直接发送字符串
class
RequestHandler
extends
Thread
{
private
Socket
socket
;
RequestHandler
(
Socket
socket
)
{
this
.
socket
=
socket
;
}
@Override
public
void
run
()
{
try
(
PrintWriter
out
=
new
PrintWriter
(
socket
.
getOutputStream
());)
{
out
.
println
(
"Hello world!"
);
out
.
flush
();
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
}
- 服务器端启动 ServerSocket,端口 0 表示自动绑定一个空闲端口。
- 调用 accept 方法,阻塞等待客户端连接。
- 利用 Socket 模拟了一个简单的客户端,只进行连接、读取、打印。
- 当连接建立后,启动一个单独线程负责回复客户端请求。
这样,一个简单的 Socket 服务器就被实现出来了。
(图片来源于杨晓峰)
三、NIO 代码实现
public
class
Nioserver
extends
Thread
{
public
void
run
()
{
try
(
Selector
selector
=
Selector
.
open
();
ServerSocketChannel
serverSocket
=
ServerSocketChannel
.
open
();)
{
// 创建 Selector 和 Channel
serverSocket
.
bind
(
new
InetSocketAddress
(
InetAddress
.
getLocalHost
(),
8888
));
serverSocket
.
configureBlocking
(
false
);
// 注册到 Selector,并说明关注点
serverSocket
.
register
(
selector
,
SelectionKey
.
OP_ACCEPT
);
while
(
true
)
{
selector
.
select
();
// 阻塞等待就绪的 Channel,这是关键点之一
Set
<
SelectionKey
>
selectedKeys
=
selector
.
selectedKeys
();
Iterator
<
SelectionKey
>
iter
=
selectedKeys
.
iterator
();
while
(
iter
.
hasNext
())
{
SelectionKey
key
=
iter
.
next
();
// 生产系统中一般会额外进行就绪状态检查
sayHelloWorld
((
ServerSocketChannel
)
key
.
channel
());
iter
.
remove
();
}
}
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
}
private
void
sayHelloWorld
(
ServerSocketChannel
server
)
throws
IOException
{
try
(
SocketChannel
client
=
server
.
accept
();)
{
client
.
write
(
Charset
.
defaultCharset
().
encode
(
"Hello world!"
));
}
}
// 省略了与前面类似的 main
}
-
- 首先,通过 Selector.open() 创建一个 Selector,作为类似调度员的角色。
-
- 然后,创建一个 ServerSocketChannel,并且向 Selector 注册,通过指定 SelectionKey.OP_ACCEPT,告诉调度员,它关注的是新的连接请求。注意:为什么我们要明确配置非阻塞模式呢?这是因为阻塞模式下,注册操作是不允许的,会抛出 IllegalBlockingModeException 异常。
-
- Selector 阻塞在 select 操作,当有 Channel 发生接入请求,就会被唤醒。
-
- 在 sayHelloWorld 方法中,通过 SocketChannel 和 Buffer 进行数据操作,在本例中是发送了一段字符串。
可以看到,在前面两个样例中,IO 都是同步阻塞模式,所以需要多线程以实现多任务处理。而 NIO 则是利用了单线程轮询事件的机制,通过高效地定位就绪的 Channel,来决定做什么,仅仅 select 阶段是阻塞的,可以有效避免大量客户端连接时,频繁线程切换带来的问题,应用的扩展能力有了非常大的提高。下面这张图对这种实现思路进行了形象地说明。
- 在 sayHelloWorld 方法中,通过 SocketChannel 和 Buffer 进行数据操作,在本例中是发送了一段字符串。
(图片来源于杨晓峰)
四、参考资料
杨晓峰《Java核心36讲》
近期热门文章:
Java 最常见的 200+ 面试题
以上是关于阿里面试题BIO和NIO数量问题附答案和代码的主要内容,如果未能解决你的问题,请参考以下文章
Java网络编程与NIO详解12:常见NIO和BIO的Java面试题,一网打尽!
分享 2021 年最新阿里 java 面试题:java 初级 + 中级 + 高级面试题(附答案),让你的面试之路畅通无阻!