1、Java BIO
1.1、基本介绍
Java BIO
: 同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销。
1)Java BIO
就是传统的java io
编程,其相关的类和接口在java.io
;
2)BIO
(blocking I/O
) : 同步阻塞**,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器);
3)BIO
方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4
以前的唯一选择,程序简单易理解。
1.2、BIO
工作机制
BIO
编程简单流程
-
服务器端启动一个
ServerSocket
; -
客户端启动
Socket
对服务器进行通信,默认情况下服务器端需要对每个客户建立一个线程与之通讯; -
客户端发出请求后, 先咨询服务器是否有线程响应,如果没有则会等待,或者被拒绝;
-
如果有响应,客户端线程会等待请求结束后,在继续执行。
1.3、案例
实例说明:
- 使用
BIO
模型编写一个服务器端,监听6666
端口,当有客户端连接时,就启动一个线程与之通讯。 - 要求使用线程池机制改善,可以连接多个客户端.
- 服务器端可以接收客户端发送的数据(
telnet
方式即可)。
package com.dult;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BIOServer {
public static void main(String[] args) throws Exception {
//线程池机制
//思路
//1. 创建一个线程池
//2. 如果有客户端连接,就创建一个线程,与之通讯(单独写一个方法)
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
//创建 ServerSocket
ServerSocket serverSocket = new ServerSocket(6666);
System.out.println("服务器启动了");
while (true) {
System.out.println(" 线 程 信 息 id =" + Thread.currentThread().getId() + " 名 字 =" +Thread.currentThread().getName());
//监听,等待客户端连接
System.out.println("等待连接....");
final Socket socket = serverSocket.accept();
System.out.println("连接到一个客户端");
//就创建一个线程,与之通讯(单独写一个方法)
newCachedThreadPool.execute(new Runnable() {
public void run() { //我们重写
//可以和客户端通讯
handler(socket);
}
});
}
}
//编写一个 handler 方法,和客户端通讯
public static void handler(Socket socket) {
try {
System.out.println(" 线 程 信 息 id =" + Thread.currentThread().getId() + " 名 字 =" +Thread.currentThread().getName());
byte[] bytes = new byte[1024];
//通过 socket 获取输入流
InputStream inputStream = socket.getInputStream();
//循环的读取客户端发送的数据
while (true) {
System.out.println(" 线 程 信 息 id =" + Thread.currentThread().getId() + " 名 字 =" +Thread.currentThread().getName());
System.out.println("read....");
int read = inputStream.read(bytes);
if(read != -1) {
System.out.println(new String(bytes, 0, read
)); //输出客户端发送的数据
} else {
break;
}
}
}catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("关闭和 client 的连接");
try {
socket.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
1.4、Java BIO
的问题分析
-
每个请求都需要创建独立的线程,与对应的客户端进行数据
Read
,业务处理,数据Write
; -
当并发数较大时,需要创建大量线程来处理连接,系统资源占用较大;
-
连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在
Read
操作上,造成线程资源浪费。
2、NIO
2.1、概述
Java NIO
: 同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O
请求就进行处理 。
Java NIO
全称 java non-blocking IO
,是指 JDK
提供的新API
。NIO
相关类都被放在java.nio
包及子包下,并且对原java.io
包中的很多类进行改写。与传统IO
区别:
2.2、NIO
和BIO
的比较
IO | NIO |
---|---|
面向流 | 面向缓冲区(或者说面向块) |
阻塞IO | 非阻塞IO |
无 | 选择器 |
-
BIO
以流的方式处理数据,而NIO
以块的方式处理数据,块I/O
的效率比流I/O
高很多 -
BIO
是阻塞的,NIO
则是非阻塞的 -
BIO
基于字节流和字符流进行操作,而NIO
基于Channel
(通道)和Buffer
(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector
(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道 。
NIO
有三大核心部分:Channel
( 通道) ,Buffer
( 缓冲区), Selector
( 选择器),之后会从源码级别细讲。
当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情,当数据被写入缓冲区时,线程可以继续处理它,从缓冲区写入通道也类似。
通俗理解:NIO
是可以做到用一个线程来处理多个操作的。假设有 10000
个请求过来,根据实际情况,可以分配50
或者100
个线程来处理。不像之前的阻塞IO
那样,非得分配 10000
个。
而且HTTP2.0
使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1
大了好几个数量级。
3、Java AIO
Java AIO
(NIO.2
) : 异步非阻塞,AIO
引入异步通道的概念,采用了Proactor
模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。
-
JDK 7
引入了Asynchronous I/O
,即AIO
。在进行I/O
编程中,常用到两种模式:Reactor
和Proactor
。Java
的NIO
就是Reactor
,当有事件触发时,服务器端得到通知,进行相应的处理; -
AIO
即NIO2.0
,叫做异步不阻塞的IO
。AIO
引入异步通道的概念,采用了Proactor
模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。
4、BIO
、NIO
、BIO
的比较
BIO | NIO | AIO | |
---|---|---|---|
IO 模型 | 同步阻塞 | 同步非阻塞(多路复用) | 异步非阻塞 |
编程难度 | 简单 | 复杂 | 复杂 |
可靠性 | 差 | 好 | 好 |
吞吐量 | 低 | 高 | 高 |
举例说明
1)同步阻塞:到理发店理发,就一直等理发师,直到轮到自己理发;
2)同步非阻塞:到理发店理发,发现前面有其它人理发,给理发师说下,先干其他事情,一会过来看是否轮到自己;
3)异步非阻塞:给理发师打电话,让理发师上门服务,自己干其它事情,理发师自己来家给你理发。