开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情
重要概念
-
阻塞IO和非阻塞IO
这两个概念指的是在请求操作系统IO后,如果IO资源没有准备好,后续怎么操作,前者等待,后者继续执行(会不断地轮询,直到有IO资源准备好)。是程序级别的。 -
同步IO和异步IO
同步IO 请求后,请求一阻塞或者轮询的方式等待IO完成,需要将内核态准备就绪的数据拷贝到用户态,需要阻塞用户态并等待IO完成
异步IO 请求后立即返回继续执行后续逻辑流,不需要阻塞
IO的分类
IO可以分为三类 BIO、NIO、AIO
BIO:同步阻塞IO,使用BIO读取数据时,线程会阻塞住,并且需要线程主动去查询是否有数据可读,并且需要处理完一个socket之后才能处理下一个socket
AIO:异步非阻塞IO,使用AIO读取数据时,现成不会阻塞,并且当有数据可读时会通知线程,不需要线程主动去查询
NIO:同步非阻塞IO,使用NIO时,线程不会阻塞,但需要线程主动的去查询是否有IO事件
BIO
BIO的工作机制
BIO的特性 每个客户端对应一个线程,即多少个客户端多少个线程
BIO的实现
server端
package com.example.io.bio;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class BIOServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8000);
// (1) 接收新连接线程
new Thread(() -> {
while (true) {
try {
// (1) 阻塞方法获取新的连接
Socket socket = serverSocket.accept();
// (2) 每一个新的连接都创建一个线程,负责读取数据
new Thread(() -> {
try {
int len;
byte[] data = new byte[1024];
InputStream inputStream = socket.getInputStream();
// (3) 按字节流方式读取数据
while ((len = inputStream.read(data)) != -1) {
System.out.println(new String(data, 0, len));
}
} catch (IOException e) {
}
}).start();
} catch (IOException e) {
}
}
}).start();
}
}
client端
package com.example.io.bio;
import java.io.IOException;
import java.net.Socket;
import java.util.Date;
public class BIOClient {
public static void main(String[] args) {
new Thread(() -> {
try {
Socket socket = new Socket("127.0.0.1", 8000);
while (true) {
try {
socket.getOutputStream().write((new Date() + ": hello world").getBytes());
Thread.sleep(2000);
} catch (Exception e) {
}
}
} catch (IOException e) {
}
}).start();
}
}
BIO的问题
- 线程资源受限:线程是操作系统中非常宝贵的资源,同一时刻有大量的线程处于阻塞状态是非常严重的资源浪费,操作系统耗不起
- 线程切换效率低下:单机 CPU 核数固定,线程爆炸之后操作系统频繁进行线程切换,应用性能急剧下降。