概述
从同步与异步&阻塞与非阻塞的概念,到具体的I/O模型,再到具体的Java语言实现,都是层层递进,本篇就从Java语言来看I/O模型的大概情况。
整个Java I/O模型,大致可以分为三类
- BIO:JDK1.4之前的阻塞IO
- NIO:JDK1.4及以后的版本非阻塞IO
- AIO:JDK1.7之后,又叫NIO.2
一、BIO阻塞IO
1、基本概念
BIO,即为Blocking I/O,阻塞IO,大致流程为:
- 1、服务端建立ServerSocket,以一个端口启动
- 2、等待客户端建立socket连接,如果没有连接,一直阻塞
- 3、一个socket建立连接之后,从线程池中去一个线程取处理socket
2、代码分析
public class BlockingIOServer {
public static void main(String[] args) throws IOException {
int port = 10000;
ExecutorService threadPool = Executors.newFixedThreadPool(10);
ServerSocket server = new ServerSocket(port);
while(true){
Socket client = server.accept();
//从线程池取线程处理client
threadPool.execute(()->{
try{
InputStream input = client.getInputStream();
//TODO read input
String req = null;
String res = "response:"+req;
//TODO response
client.getOutputStream().write(res.getBytes());
}catch(IOException e){
e.printStackTrace();
}finally {
try {
client.close();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
}
3、总结
- 如果请求量过大,线程池不够用,那么会严重影响性能。CPU疲于切换线程,执行的效率降低。
-
现在tomcat I/O模型默认还是BIO。
-
但是连接不大,该模型还是非常具有优越性,代码编写简单,只需要关注该线程内的连接即可。
-
BIO模型,也就是同步非阻塞模型。
二、NIO非阻塞IO
1、基本概念
-
NIO,即是Non Blocking I/O,非阻塞IO。
-
在JDK1.4及以后版本中提供了一套API来专门操作非阻塞I/O,接口以及类定义在java.nio包。由于这套API是JDK新提供的I/O API,因此,也叫New I/O。
- NIO API由四个主要的部分组成:缓冲区(Buffers)、通道(Channels)、选择器(Selector)和非阻塞I/O的核心类组成。
NIO 的工作大致流程为:
- 1、通道注册一个监听到事件处理器
- 2、有事件发生时,事件处理器会通知相应的通道处理
2、代码分析
public class NonBlockingIOServer {
private int BLOCK = 4096;
private ByteBuffer sendBuffer = ByteBuffer.allocate(BLOCK);
private ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK);
private Selector selector;
public NonBlockingIOServer(int port) throws IOException {
//1.open ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.configureBlocking false
serverSocketChannel.configureBlocking(false);
//3.bind port
serverSocketChannel.socket().bind(new InetSocketAddress(port));
//4.open Selector
selector = Selector.open();
//5.serverSocketChannel register select
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server Start,port:"+port);
}
private void accept() throws IOException {
while (true) {
// 1.select,block
selector.select();
// 2.SelectionKey iterator
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
try {
doAccept(selectionKey);
} catch (IOException e) {
selectionKey.cancel();
e.printStackTrace();
}
}
}
}
private void doAccept(SelectionKey selectionKey)throws IOException{
if (selectionKey.isAcceptable()) {
// ServerSocketChannel 的 selectionKey
ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
if(null == server){
return;
}
//接受到此通道套接字的连接,block here
SocketChannel client = server.accept();
// 配置为非阻塞
client.configureBlocking(false);
// 注册读到selector,等待读的selectionKey
client.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// SocketChannel 的 selectionKey
SocketChannel client = (SocketChannel) selectionKey.channel();
receiveBuffer.clear();
int count = client.read(receiveBuffer);
if (count > 0) {
String receiveText = new String( receiveBuffer.array(),0,count);
System.out.println(receiveText);
//注册写到selector,等待读的selectionKey
SelectionKey key = client.register(selector, SelectionKey.OP_WRITE);
//这里可以作为设计框架的扩展之处
key.attach(receiveText);
}
} else if (selectionKey.isWritable()) {
// SocketChannel selectionKey
SocketChannel client = (SocketChannel) selectionKey.channel();
//取出read 的 attachment
String request = (String) selectionKey.attachment();
String sendText="response--" + request;
sendBuffer.clear();
sendBuffer.put(sendText.getBytes());
sendBuffer.flip();
//输出到通道
client.write(sendBuffer);
System.out.println(sendText);
client.register(selector, SelectionKey.OP_READ);
}
}
/**
* [[@param](http://my.oschina.net/u/2303379)](http://my.oschina.net/u/2303379) args
* [[@throws](http://my.oschina.net/throws)](http://my.oschina.net/throws) IOException
*/
public static void main(String[] args) throws IOException {
int port = 10000;
NonBlockingIOServer server = new NonBlockingIOServer(port);
server.accept();
}
}
主要流程为:
- 1、open ServerSocketChannel,configureBlocking false,bind host and port
- 2、open Selector
- 3、ServerSocketChannel register on Selector
- 4、有客户端连接的事件发生,事件处理器通知ServerSocketChannel去处理
3、总结
- NIO本身是基于事件驱动思想来完成的,即是Reactor模式。
- 在使用传统同步I/O模型如果要同时处理多个客户端请求,就必须使用多线程来处理。也就是说,将每一个客户端请求分配给一个线程来单独处理。这样可以达到我们的要求,但是如果客户端的请求过多,服务端程序可能会因为不堪重负而拒绝客户端的请求,甚至服务器可能会因此而瘫痪。
-
而NIO基于Selector,当有感兴趣的事件发生时,就通知对应的事件处理器去处理事件,如果没有,则不处理。当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。所以使用一个线程做轮询就可以了。
-
Buffer,也是NIO的一个新特性,可以块状的读/写数据,效率得到极大的提高。
-
所以NIO提高了线程的利用率,减少系统在管理线程和线程上下文切换的开销。
三、AIO异步非阻塞IO
1、基本概念
- AIO,即是Asynchronous I/O,异步非阻塞I/O
- JDK1.7之后,引入NIO.2,也叫作AIO,工作方式是异步非阻塞
AIO主要工作流程为:
- 客户端发起一个IO调用
- 服务端接受IO之后,异步回调接收成功后的IO,不会阻挡当前主流程,主流程继续接受下一个请求
2、代码分析
public class AsynchronousIOServer {
private static Charset charset = Charset.forName("UTF-8");
public static void main(String[] args) {
int port = 10000;
int processors = Runtime.getRuntime().availableProcessors();
ExecutorService threadPool = Executors.newFixedThreadPool(processors);
try {
AsynchronousChannelGroup group = AsynchronousChannelGroup.withThreadPool(threadPool);
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group);
server.bind(new InetSocketAddress(port));
doAccept(server);
group.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
System.out.println("close server");
System.exit(0);
}
}
private static void doAccept(AsynchronousServerSocketChannel server) {
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
@Override
public void completed(AsynchronousSocketChannel client, Void attachment) {
server.accept(null, this);// accept next client connect
doRead(client, attachment);
}
@Override
public void failed(Throwable exc, Void attachment) {
exc.printStackTrace();
}
});
}
private static void doRead(AsynchronousSocketChannel client, Void attachment) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
if (result <= 0) {
try {
System.out.println("客户端断线:" + client.getRemoteAddress().toString());
attachment = null;
} catch (IOException e) {
e.printStackTrace();
}
return;
}
attachment.flip();
String req = charset.decode(attachment).toString();
attachment.compact();
client.read(attachment, attachment, this);// next client read
/** do service code **/
System.out.println(req);
ByteBuffer resBuffer = ByteBuffer.wrap(("response:" + req).getBytes());
doWrite(client, resBuffer, resBuffer);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}
private static <V> void doWrite(AsynchronousSocketChannel client, ByteBuffer resBuffer, ByteBuffer attachment) {
client.write(attachment, attachment, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
// TODO write success
if (result <= 0) {
try {
System.out.println("客户端断线:" + client.getRemoteAddress().toString());
attachment = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}
}
主要流程为:
- 1、创建一个异步非阻塞服务端
- 2、服务端接受一个请求,异步回调接受成功后的IO请求,然后继续接受下一个请求
- 3、异步回调请求的IO,读取请求数据成功后,异步回调读取后的结果,然后继续读下面的数据,不会阻塞当前IO读
- 4、异步回调的读IO数据,然后同步处理数据,这里可能是计算逻辑,所以这里也是性能的瓶颈之处,如果是计算密集型,AIO模型不适用,处理完成之后,异步写数据到IO请求
3、总结
- 与NIO不同,NIO每次都是事件通知,代码处理时异常复杂,而AIO当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的
- 对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并异步回调通知应用程序;
- 对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。
- 在JDK1.7中,这部分内容被称作NIO.2
- select/poll/epoll/iocp。在Linux 2.6以后,java NIO的实现,是通过epoll来实现的,这点可以通过jdk的源代码发现。
- 而AIO,在windows上是通过IOCP实现的,在linux上还是通过epoll来实现的。
- 这里强调一点:AIO,这是I/O处理模式,而epoll等都是实现AIO的一种编程模型;换句话说,AIO是一种接口标准,各家操作系统可以实现也可以不实现。在不同操作系统上在高并发情况下最好都采用操作系统推荐的方式。Linux上还没有真正实现网络方式的AIO。
四、大总结
1、文中所用代码
2、三种I/O模型适用场景
-
BIO方式适用于连接数量小,连接时间短,计算密集,代码编写直观,程序直观简单易理解,JDK1.4之前。
-
NIO方式适用于连接数量大,连接时间短,比如Http服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
-
AIO方式使用于连接数量大,连接时间长,IO密集型,比如聊天服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
另外要清楚理解的:
- I/O属于底层操作,需要操作系统支持,并发也需要操作系统的支持,所以性能方面不同操作系统差异会比较明显。
- AIO是操作系统准备好数据之后通知应用程序,而NIO是程序不断的轮询操作系统是否有准备好数据。
五、文章引用
相关推荐
总的来说,Java的I/O编程模型通过流的概念提供了一种灵活且易于使用的接口,允许开发者处理各种类型的数据源,同时隐藏了数据转换的复杂性。理解和掌握I/O编程,对任何Java开发者来说都是至关重要的。
5. **输入/输出与NIO**:讲解Java的I/O流体系,包括字节流、字符流、对象序列化,以及非阻塞I/O(New I/O,NIO)框架的使用。 6. **多线程编程**:介绍线程的创建、同步、协作,以及线程池的使用。 7. **反射与...
BIO(Blocking I/O)、NIO(Non-blocking I/O)和AIO(Asynchronous I/O)是Java中三种不同的I/O模型。BIO是传统的同步阻塞方式,每个连接都需要一个独立的线程来处理,当并发量大时,线程资源消耗较大。NIO则引入了...
Java 中的 NIO(New Input/Output)是一种非阻塞的 I/O 模型,它在 JDK 1.4 版本中引入,为 Java 应用提供了更高性能和更低延迟的数据传输方式。NIO 与传统的 I/O( Blocking I/O)相比,有以下几个关键区别: 1. *...
11. **Java I/O与NIO**:深入学习Java的I/O模型,包括NIO(非阻塞I/O)的使用。 12. **反射与注解**:理解反射机制,允许在运行时动态访问和修改类的信息,以及注解在代码中扮演的角色。 13. **JavaFX与Swing**:...
4. **NIO(非阻塞I/O)**:介绍了Java的新I/O模型,包括选择器、通道和缓冲区,用于提高I/O性能。 5. **JavaFX**:如果第一卷涉及到Swing,那么第二卷可能涵盖现代的JavaFX库,用于构建更复杂的图形用户界面。 6. ...
3. **NIO(非阻塞I/O)**:介绍Java的非阻塞I/O模型,包括Channel、Buffer和Selector。 4. **反射**:详细讨论如何在运行时检查和操作类、接口和对象,包括Class类、Method、Field和Constructor。 5. **JVM内部机制*...
7. **JVM工作原理**:简述了Java虚拟机(JVM)的工作流程,包括类加载、字节码执行、内存模型(堆、栈、方法区等)以及垃圾回收机制。 8. **SCJP考试策略**:书中可能包含了模拟试题、解答技巧和考试策略,帮助考生...
5. **IO/NIO/BIO**:对比分析三种I/O模型的优缺点,重点讲解非阻塞I/O(NIO)的应用。 6. **Java虚拟机(JVM)**:介绍JVM的工作原理,包括类加载机制、内存管理(堆、栈、方法区等)、垃圾回收机制以及性能优化...
5. **输入/输出(I/O)**:讲解流的概念,包括文件I/O、网络I/O和NIO(非阻塞I/O),以及如何进行数据序列化和反序列化。 6. **反射与注解**:解释反射机制,如何在运行时动态获取类信息并操作对象,以及注解的使用和...
- BIO(Blocking I/O):传统的同步阻塞I/O模型,每个连接对应一个线程,当连接数量增多时,线程资源消耗较大。 - NIO(Non-blocking I/O):非阻塞I/O,基于通道(Channel)和缓冲区(Buffer)进行数据读写,通过...
《Java核心技术电子书原版》包含了两卷:《Core Java (Volume I--Fundamentals 9th Edition)》和《Core Java (Volume II--Advanced Features 9th Edition)》。这两卷书籍是Java开发者的重要参考资料,由Cay S. ...
6. **I/O和NIO**:介绍NIO(非阻塞I/O)和AIO(异步I/O)的原理和使用,以及在高并发场景下的优势。 7. **Java Swing和JavaFX图形界面编程**:教授如何构建桌面应用程序的用户界面,包括组件使用、布局管理器以及...
5. **输入/输出(I/O)**:介绍Java的流模型,包括文件I/O、网络I/O以及序列化。 6. **线程**:讲解并发编程的基础,如何创建和管理线程,以及同步和互斥机制。 7. **反射**:讨论Java的动态类型特性,如何在运行时...
4. **文件I/O流编程**:读写文件、数据序列化等。 5. **网络编程**:TCP/IP协议的应用,如客户端/服务器模型。 6. **多线程编程**:利用多线程提高程序性能。 #### Java EE基础知识 1. **Java面向对象编程**:与...
10. **Java内存模型和垃圾回收**:解析Java内存区域,探讨对象的生命周期,以及GC的工作原理和性能优化。 11. **IO流新特性NIO**:介绍非阻塞I/O,Channel、Buffer和Selector等NIO核心组件,以及NIO在高并发场景下...
8. **输入/输出(I/O)**:讨论了Java的流模型,包括文件操作、序列化、网络I/O和NIO(New I/O)框架。 9. **反射机制**:介绍如何在运行时检查类的信息,动态创建对象,调用方法,修改字段值,以及代理模式。 10. *...
7. **输入/输出流**:解释Java的I/O系统,包括文件操作、流的概念、缓冲区和对象序列化。 8. **多线程**:探讨并发编程,线程的创建、同步、死锁问题,以及java.util.concurrent包提供的高级并发工具。 9. **Java ...
三、Java I/O流 7. 文件操作:学习读写文件的基本方法,包括File类的使用和BufferedReader/BufferedWriter等流的运用。 8. 字节流与字符流:了解InputStream和OutputStream代表的字节流,以及Reader和Writer代表的...
I/O流在处理数据输入输出时至关重要,PPT可能会简述Java的流模型,包括文件I/O、字符流和字节流,以及缓冲区的概念。 最后,PPT可能还会涉及到线程和并发,解释如何在Java中创建和管理线程,以及同步和互斥的概念,...