理解阻塞和非阻塞
- 阻塞:在调用结果返回之前,当前线程被挂起;
- 非阻塞:在不能立刻收到返回结果之前,本次调用不会阻塞当前线程;
- 阻塞非阻塞强调服务端程序在等待结果时的状态。
理解同步和异步
- 同步:客户端发出请求后,在没有收到返回结果之前,一直阻塞;
- 异步:客户端发出请求后,立即返回,但可能没有结果;等服务端结束后回调客户端;
- 同步异步强调客户端程序在等待结果时的状态。
BIO
我们通过一个简单的程序来了解下BIO
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author wangzuojia
* @data 2020/8/21 16:17
* @description 模拟同步阻塞BIO服务端
*/
public class BIOServer {
static byte[] bs = new byte[1024];
public static void main(String[] args) throws IOException {
//它只是一个监听机制
ServerSocket socket = new ServerSocket(8090);
while (true){
System.out.println("BIOServer start block");
//阻塞,监听到新的连接请求
Socket clientSocket = socket.accept();
System.out.println("BIOServer connection successful");
//阻塞,直到有网络连接输入
clientSocket.getInputStream().read(bs);
System.out.println("read data success");
System.out.println(new String(bs));
}
}
}
运行BIOServer查看结果
从输出结果我们看到程序一直在阻塞。此处,ServerSocket socket = new ServerSocket(8090);应用程序调用内核kernel做了三件事情:
- socket = 3;
- bind(3,9090),给socket绑定9090端口;
- listen(3)。
进入while代码块socket.accept();时,一直处于阻塞状态,内核执行:accept(3, 如图:
下面运行客户端,模拟BIO整个执行过程,代码如下:
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;
/**
* @author wangzuojia
* @data 2020/8/21 16:29
* @description 模拟同步阻塞BIO客户端
*/
public class BIOClient {
public static void main(String[] args) {
try {
// 绑定服务端8090端口
Socket socket = new Socket("127.0.0.1", 8090);
Scanner scanner = new Scanner(System.in);
String next = scanner.next();
socket.getOutputStream().write(next.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端运行结果如下图:
BIOClient输入:BIOClient点击回车:
然后我们在看BIOServer端,从下图结果可得到程序发生阻塞,等待下一个连接:
当有新连接请求时,内核实际上克隆了一个新线程用以处理客户端数据,new thread - 》clone 1259
BIO模式结论
- 每一个连接请求,新建一个线程;
- CPU单位时间内,轮训执行线程;
- 处理要处理代码逻辑,还有额外的内核操作(注:System.out.println()也是内核操作,线上环境禁用);
- 随着线程增加,浪费CPU资源。