同步,异步,阻塞,非阻塞
- 同步:a 事件必须等到 b 事件完成才可以继续执行/返回
- 异步:a 事件可以先执行/返回,不需要等待 b 事件的完成,而是通过回调处理 b 事件的返回结果
- 阻塞:当发起一次请求时,调用者一直等待结果的返回,只有当条件满足时,才继续处理后续的工作
- 非阻塞:当发起一次请求时,调用者不用一直等待结果的返回,可以先去做其他的事情
1、BIO(Blocking IO)
同步阻塞 io 模型,一个连接一个线程,即当客户端有连接请求时就会创建一个线程去处理连接请求,会造成不必要的开销,可以通过线程池来改善
1.1、代码示例
import java.io.IOException;
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 IOException {
// 创建 server
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器启动");
while (true){
// 主线程等待客户端的连接
System.out.println(Thread.currentThread().getName() + " 等待客户端连接");
// 等待客户端连接
Socket socket = serverSocket.accept();
// 创建一个线程处理当前请求
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
newCachedThreadPool.execute(() -> {
// 处理连接
try {
System.out.println("客户端 " + Thread.currentThread().getName() + " 已连接");
handler(socket);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
private static void handler(Socket socket) throws IOException {
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
System.out.println("等待客户端" + Thread.currentThread().getName() + "发送数据");
int read = inputStream.read(bytes);
System.out.println("客户端 " + Thread.currentThread().getName() + " 发送数据");
while (read != -1){
System.out.println(new String(bytes));
read = inputStream.read(bytes);
}
}
}
1.2、问题
- 当有大量客户端连接时,就需要创建大量的线程,资源占用较大
- 连接建立后如果没有当前线程暂时没有可读内容,就会阻塞,accept,read,write 都会阻塞线程
2、NIO(non-blocking IO)
同步非阻塞 io 模型,面向缓冲区
三大核心部分:
2.1、selector(多路复用器)
能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。这样一来, 只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程, 不用去维护
多个线程,并且避免了多线程之间的上下文切换导致的开销。
2.2、buffer(缓冲区)
一个可以读写的内存块,底层是数组实现的,在 BIO 中是采用 stream 流的方式进行读写,在 NIO 中读取时直接从 buffer 中读取,发送时直接写入 buffer 中
2.3、channel(通道)
NIO 是通过 channel 进行读写,读写可以在一个通道进行,而 BIO 中的流只能读或者写,常用的 channel 有 FileChannel,ServerSocketChannel 相当于 BIO 中的 serverSocket,SocketChannel 相当于 BIO 中的 socket
2.4、代码
服务端:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
/**
* 服务器端
*/
public class NioServer {
public static void main(String[] args) throws IOException {
// 获取服务器通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 绑定端口
serverSocketChannel.bind(new InetSocketAddress(6666));
System.out.println("服务器已经启动");
// 获取选择器
Selector selector = Selector.open();
// 设置为非阻塞
serverSocketChannel.configureBlocking(false);
// 将服务器通道注册到选择器中
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("将服务器通道注册到选择器中");
while (true){
// 没有事件发生
if (selector.select(1000) == 0) {
continue;
}
// 获取所有的已选择的事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
// 拿到 selectionKeys 的迭代器
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
// keyIterator.forEachRemaining();
while (keyIterator.hasNext()){
SelectionKey selectionKey = keyIterator.next();
// 是连接事件
if (selectionKey.isAcceptable()){
// 获取客户端通道
SocketChannel socketChannel = serverSocketChannel.accept();
// 设置为非阻塞
socketChannel.configureBlocking(false);
// 将客户端通道注册到选择器上
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
System.out.println("客户端连接成功");
}
// 读取事件
if (selectionKey.isReadable()){
// 获取当前事件的缓冲区对象
ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
// 获取当前事件对应的通道
SocketChannel channel = (SocketChannel) selectionKey.channel();
System.out.println("读取之前缓冲区的数据:" + new String(byteBuffer.array()));
// 将通道中的数据读取到缓冲区中
channel.read(byteBuffer);
System.out.println("读取之后缓冲区的数据:" + new String(byteBuffer.array()));
byteBuffer.clear();
}
keyIterator.remove();
}
}
}
}
客户端:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NioClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 6666));
socketChannel.configureBlocking(false);
String str = "hello word";
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
socketChannel.write(byteBuffer);
}
}
3、AIO(Asynchronous IO)
在 io 编程中,常用有俩种模式,reactor 和 proactor,NIO 就是 reactor,当有事件触发时,服务端得到通知,进行相应的处理,AIO 是异步非阻塞,采用 Proactor 模式,有效的请求才启动线程
4、不同 io 模型的对比
下图来源于 <<netty 权威指南>>