相关系列文章
1. 高并发编程系列之IO模型(一)-------- BIO教程
2. 高并发编程系列之IO模型(二)-------- NIO教程
同步 与 异步 的区别
同步: 发送一个请求,必须等待返回,才能再发送下一个请求 ,能够避免死锁,数据脏读的发生
异步: 发送一个请求,你不用等待返回,可以接着发送另一个请求,适合并发的场景
举例: 烧开水,将水壶放在燃气灶上,你不能取干别的(只能在哪里老老实实的等着水烧开,这就是同步)
要是烧水期间,你取做别的,看看报纸,浇浇花,水烧开了,有一个信号通知你,这就是异步完成
阻塞和非阻塞
阻塞
传统的IO流都是阻塞式的。也就是说,当一个线程调用read()或者write()方法时,该线程将被阻塞,直到有一些数据读读取或者被写入,在此期间,该线程不能执行其他任何任务。在完成网络通信进行IO操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量的客户端时,性能急剧下降。
非阻塞
NIO是非阻塞式的。当线程从某通道进行读写数据时,若没有数据可用时,该线程会去执行其他任务。线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作,所以单独的线程可以管理多个输入和输出通道。因此NIO可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。
BIO模型实现原理图
BIO是JDK1.4之前 Java中的IO实现,每一个请求,都创建一个新的线程,这样导致在高并发的情况下,后端有非常多的线程创建和销毁操作,同时还有
依赖的核心类
客户端:
Socket
输入输出流的处理类
服务端:
ServerSocket
输入输出流的处理类
BIO的优势
编码简单,真的相比较非常简单
线程安全(不会出现死锁,脏读的问题)
BIO的弊端
BIO是面向流操作的,读取操作都非常的慢
同时 流操作的方法都是阻塞的(有兴趣可以看看JDK的实现)
这种方式,对于服务器的资源占用比较大(需要不断的创建新的线程来处理新的连接,同时GC清理失效的线程,这里可以使用线程池的解决方案实现一定的优化,但是没有改变同步阻塞IO的本质)
BIO的使用的场景
在业务的并发量并不是非常大的情况下,选择BIO是非常合适的,编码简单,不容易出错,但是实际的开发中我们很少使用JDK的原生的实现来开发项目
项目代码
服务端代码:
/**
* @author : GONG ZHI QIANG
* @data : 2019-09-06 , 13:46
* @user : SnaChat
* @project: Netty
* @description :
* <p>
* BIO 模型的实现
*/
public class BIOService {
public static void main(String[] args) {
final int SERVER_PORT = 8080;
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(SERVER_PORT);
Socket socket = null;
System.out.println("BIO服务端---创建成功");
while (true) {
//等待客户端的连接,这个方法是阻塞的,一直等到有客户端来连接,线程才会被唤醒
socket = serverSocket.accept();
// 创建一个新的线程来处理任务
new Thread(new BIOServerHandler(socket)).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (serverSocket != null) {
System.out.println("服务端即将关闭");
try {
serverSocket.close();
} catch (IOException e) {
serverSocket = null;
e.printStackTrace();
}
}
}
}
}
服务端接受到的客户端的通信(截图)
客户端的代码
public class BIOClient {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
clientMethods();
}
}
public static void clientMethods() {
final int CLIENT_PORT = 8080;
final String HOST = "127.0.0.1";
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket(HOST, CLIENT_PORT);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
out.println("你好!我是客户端,你能接受到这条信息吗?");
System.out.println("客户端发送信息成功");
String resp = in.readLine();
System.out.println("服务端 : " + resp);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//这里没有判断,简写了
in.close();
out.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端接受到的服务端返回的数据(截图)
服务端处理器(对请求逻辑处理)
public class BIOServerHandler extends Thread {
private Socket socket;
public BIOServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
super.run();
BufferedReader in = null;
PrintWriter out = null;
try {
//输入流 和输出流的 方法也是阻塞的
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(), true);
String returnMessage = null;
String clientMessage = null;
while (true) {
clientMessage = in.readLine();
if (clientMessage == null) break;
System.out.println("客户端 :" + clientMessage);
returnMessage = "你好,服务端已经接受到你的信息,正在处理中 , Time =" + new Date(System.currentTimeMillis()).toString();
//将信息发送到客户端
out.println(returnMessage);
}
} catch (IOException e) {
if (in != null) {
try {
in.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
if (out != null) {
out.close();
out = null;
}
if (this.socket != null) {
try {
this.socket.close();
} catch (IOException ex) {
ex.printStackTrace();
}
this.socket = null;
}
}
}
}
下面是使用线程池优化的方案(仅仅是实现了线程的复用,并不改变同步阻塞IO的本质)
创建线程池,并就收客户端的连接
public class BIOThreadPoolServer {
public static void main(String[] args) {
final int SERVER_PORT = 8080;
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(SERVER_PORT);
Socket socket = null;
//创建线程池
BIOThreadPoolServerHandler pool = new BIOThreadPoolServerHandler(20, 2000);
System.out.println("创建线程连接池成功,等待客户端的访问");
while (true) {
socket = serverSocket.accept();
pool.executor(new BIOServerHandler(socket));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
serverSocket = null;
}
}
}
}
使用线程池执行任务
public class BIOThreadPoolServerHandler {
private ExecutorService executorService;
public BIOThreadPoolServerHandler(int maxPoolSize, int queueSize) {
/**
* 初始化线程池的大小 , 这里根据 机器的处理器的核心数量动态的创建线程池核心线程大小
*/
executorService = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), maxPoolSize, 120L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize));
}
/**
* 执行任务
* @param task
*/
public void executor(Runnable task) {
executorService.execute(task);
}
}