BIO 线程模型
BIO I/O模型
阻塞非阻塞定义
阻塞和非阻塞指的是调用者(程序)在等待返回结果(或输入)时的状态。
阻塞时,在调用结果返回前,当前线程会被挂起,并在得到结果之后返回。
非阻塞时,如果不能立刻得到结果,则该调用者不会阻塞当前线程。因此对应非阻塞的情况,调用者需要定时轮询查看处理状态。
– 百度百科
简单的说就是b被a调用 b在响应结果给a回复之前,a是否一直在等着,如果a等着则就可以说是阻塞的
同步处理与异步处理
同步处理是指被调用方得到最终结果之后才返回给调用方;
异步处理是指被调用方先返回应答,然后再计算调用结果,计算完最终结果后再通知并返回给调用方。
BIO单线程实现理解
1.服务端实现
public static void main(String[] args) {
byte[] bytes = new byte[1024];
try {
ServerSocket serverSocket = new ServerSocket();
//监听指定端口
serverSocket.bind(new InetSocketAddress(9999));
//循环等待主动连接请求,线程会进行两次阻塞
while (true) {
System.out.println("等待连接中");
//第一次阻塞,等待新的连接进来
Socket accept = serverSocket.accept();
System.out.println("收到新的连接");
System.out.println("等待该连接数据到来");
//第二次阻塞,连接成功后接收客户端数据数据过程中阻塞,如果不客户端不关闭连接会一直阻塞在此
while (accept.getInputStream().read(bytes) > 0) {
java.lang.String s = new java.lang.String(bytes);
System.out.println("接收到的数据:" + s);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
2.客户端实现
public static void main(String[] args) {
Socket socket=null;
try {
//连接服务端
socket = new Socket("127.0.0.1", 9999);
//当前线程停止2秒,模拟服务端二次阻塞场景
Thread.sleep(2000);
//输出数据
socket.getOutputStream().write("I am the client".getBytes());
} catch (IOException e) {
e.printStackTrace();
}finally {
if (socket!=null){
try {
//释放连接
socket.close();
} catch (IOException ignored) {
}
}
}
}
运行结果
等待连接中
2021-11-19 10:21:52 >> 收到新的连接
2021-11-19 10:21:52 >> 等待该连接数据到来
2021-11-19 10:21:54 >> 接收到的数据:i
2021-11-19 10:21:54 >> 接收到的数据:
2021-11-19 10:21:54 >> 接收到的数据:a
2021-11-19 10:21:54 >> 接收到的数据:m
2021-11-19 10:21:54 >> 接收到的数据:
2021-11-19 10:21:54 >> 接收到的数据:c
2021-11-19 10:21:54 >> 接收到的数据:l
2021-11-19 10:21:54 >> 接收到的数据:i
2021-11-19 10:21:54 >> 接收到的数据:e
2021-11-19 10:21:54 >> 接收到的数据:n
2021-11-19 10:21:54 >> 接收到的数据:t
等待连接中
运行结果可知,客户端和服务端连接会进行两次阻塞
1.服务端启动便会进入阻塞状态,直到等到客户端连接(第一次阻塞)
2.客户端连接成功后,如果客户端不发送数据,服务端会一直等待客户发送数据,如果发送则处理,然后接着阻塞等待新的数据,直到客户端关闭连接(第二次阻塞)
代码得出结论
同一时刻服务端只能处理一个客户端的请求,如果其他客户端进入是服务端是无法响应的,此时服务端正在已经连入的一个连接的阻塞状态中, 只有等到已经连接的客户端断开连接,才可处理下一个客户端的请求
也就是说单线程情况下,BIO无法连接多个客户端
BIO多线程实现理解
1.服务端实现
public static void main(String[] args) {
byte[] bytes = new byte[11];
try {
ServerSocket serverSocket = new ServerSocket();
//监听指定端口
serverSocket.bind(new InetSocketAddress(9999));
//循环等待主动连接请求,线程会进行两次阻塞
while (true) {
System.out.println(Thread.currentThread().getName()+" 等待连接中");
//第一次阻塞,等待新的连接进来
Socket accept = serverSocket.accept();
//开辟新线程单独处理
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+" 收到新的连接");
System.out.println(Thread.currentThread().getName()+" 等待该连接数据到来");
try {
//连接后阻塞等待连接数据输入
while (accept.getInputStream().read(bytes) > 0) {
String s = new String(bytes);
System.out.println(Thread.currentThread().getName()+" 接收到的数据:" + s);
}
}catch (Exception e){
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
2.客户端实现
public static void main(String[] args) {
Socket socket = null;
//发起多个客户端连接请求
for (int i = 0; i < 3; i++) {
try {
//连接服务端
socket = new Socket("127.0.0.1", 9999);
//输出数据
socket.getOutputStream().write("i am client".getBytes());
} catch (Exception ignored) {
}finally {
try {
//释放数据,如不释放服务端会一直进行阻塞状态 等待输出数据给他
if (socket!=null){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行结果
main 等待连接中
main 等待连接中
Thread-0 收到新的连接
main 等待连接中
Thread-0 等待该连接数据到来
Thread-1 收到新的连接
Thread-1 等待该连接数据到来
main 等待连接中
Thread-2 收到新的连接
Thread-2 等待该连接数据到来
Thread-2 接收到的数据:i am client
Thread-0 接收到的数据:i am client
Thread-1 接收到的数据:i am client
代码得出结论
可以看出多线程BIO情况下,一个服务端可以处理多个客户端的数据连接
但是问题也是显而易见
1.线程过度开销,一个客户端连接就开辟一个线程,1W个连接则要开辟1W个 十分浪费服务器资源
2.线程不充分利用, 一个客户端连接如果不进行数据交互,那么服务端线程则会阻塞在等待数据交互环节,空浪费
BIO弊端
1.多个客户端连接服务端,服务端需要为每个客户端单独开辟线程来进行处理2.多线程处理后,客户端只进行连接不操作数据,造成的线程读取数据阻塞,空占资源
BIO优化
1. i/o多路复用,引用操作系统机制 了解 Reactor(反应器模型) 2. 线程池 采用线程池的方式来优化客服端连接服务端每次都开辟新的线程问题,减少开销