浅谈同步与异步、阻塞非阻塞、BIO(demo)
一、 同步与异步:
用户线程和内核的交互方式
同步:用户线程发起IO操作需要等待或者轮询内核是否完成IO操作
异步:用户线程发起IO操作后无需等待,可以执行其它操作
二、 阻塞与非阻塞:
用户线程调用内核IO操作时的状态
阻塞:用户线程调用内核IO后被挂起
非阻塞:用户线程调用IO后直接返回状态,回调函数通知
一个例子(引自知乎):
1.老张把水壶放到火上,立等水开。(同步阻塞)
2.老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)
买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
3.老张把响水壶放到火上,立等水开。(异步阻塞)
老张觉得这样傻等意义不大
4.老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)
所谓同步异步,只是对于水壶而言。
普通水壶,同步;响水壶,异步。
虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。
同步只能让调用者去轮询自己(情况2中),造成老张效率的低下,cpu密集型。
所谓阻塞非阻塞,仅仅对于老张而言。
立等的老张,阻塞;看电视的老张,非阻塞。
简单的说:在处理IO的时候,阻塞和非阻塞都是同步IO。
只有使用了特殊的API才是异步IO。
三、socket
套接字,封装通信协议为网络通信提供接口,两台主机之间逻辑连接的端点,一般封装ip,端口号,协议
java网络通信简单流程
-
服务端实力化ServerSocket对象(绑定端口)
-
服务端调用accept()方法监听客户端的请求
-
服务端等待时,客户端实例化一个socket对象,指定服务器名称和端口号连接
-
连接建立成功,服务端会返回一个新的socket,用来和客户端进行通信
四、BIO
BIO代码:
客户端:
public class Client {
private int port = 12345;
private static String serverIp = "localhost";
public Client(int port) {
this.port = port;
}
public void send() {
Scanner sc = new Scanner(System.in);
try (Socket socket = new Socket(serverIp, port);
//创建流
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {
System.out.println("请输入一个数字:");
int a = sc.nextInt();
//向服务端写入数据
out.println(a);
//读取服务端返回结果
System.out.println("服务端结果为:" + in.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端
public class Server {
//单例
private static ServerSocket serverSocket;
public synchronized static void start(int port) throws IOException {
if(serverSocket != null) return;
try {
serverSocket = new ServerSocket(port);
System.out.println("服务器已启动,端口号:" + port);
Socket socket;
//死循环监听
while (true) {
//监听到客户端通信建立连接,完成三次握手
socket = serverSocket.accept();
//每次建立连接开启线程处理
new Thread(new ServerHandle(socket)).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally{
//一些必要的清理工作
if(serverSocket != null){
System.out.println("服务器已关闭。");
serverSocket.close();
serverSocket = null;
}
}
}
}
测试通信:
public class Test {
public static void main(String[] args) throws InterruptedException {
//运行服务器
new Thread(new Runnable() {
@Override
public void run() {
try {
//ServerForPool.start(12345);
Server.start(12345);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(100);
Client client = new Client(12345);
client.send();
}
}
上面说了这种情况非常消耗资源,可以稍微改进一下,加个线程池,这样就不会无限创建线程处理连接:
public class ServerForPool {
private static int port = 12345;
private static ServerSocket serverSocket;
private static ExecutorService executorService = Executors.newFixedThreadPool(60);
public synchronized static void start(int port) throws IOException {
if(serverSocket != null)
return;
try {
serverSocket = new ServerSocket(port);
System.out.println("服务器已启动:" + port);
Socket socket;
while (true) {
socket = serverSocket.accept();
executorService.execute(new ServerHandle(socket));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(serverSocket != null){
System.out.println("服务器已关闭。");
serverSocket.close();
serverSocket = null;
}
}
}
}