文档章节

java I/O 模型简述

haoran_10
 haoran_10
发布于 2016/07/14 16:40
字数 2403
阅读 588
收藏 51
点赞 3
评论 5

概述

从同步与异步&阻塞与非阻塞的概念,到具体的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)](http://my.oschina.net/u/2303379) args
	 * [[[@throws](http://my.oschina.net/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是程序不断的轮询操作系统是否有准备好数据。

五、文章引用

© 著作权归作者所有

共有 人打赏支持
haoran_10
粉丝 25
博文 88
码字总数 80846
作品 0
杭州
程序员
加载中

评论(5)

haoran_10
haoran_10

引用来自“juoliii”的评论

bio是同步非阻塞??别误导别人啊
笔误,抱歉,已更正。
j
juoliii
bio是同步非阻塞??别误导别人啊
yule
yule
good
叶平平
79
开源中国刘德华
开源中国刘德华
写的很好
JAVA程序员面试题整理(较全面)

以下是在面试中可能会遇到的问题,话不多说,往下看 1、面向对象的特征有哪些方面? 2、访问修饰符public,private,protected,以及不写(默认)时的区别? 3、String 是最基本的数据类型吗? ...

编程大侠 ⋅ 04/09 ⋅ 0

🛠VS Code编辑器配置Java开发环境

🛠VS Code编辑器配置Java开发环境 一、简述 及讨论 由于学校课程的原因,是基本以java开发为主线的课程,但是我对java兴趣不大,又加上我不太喜欢 「Eclipse」这个java的集成开发环境(简称...

Cc卿 ⋅ 06/02 ⋅ 0

Java NIO 机制分析(一) Java IO的演进

一、引言 Java1.4之前的早期版本,Java对I/O的支持并不完善,开发人员再开发高性能I/O程序的时候,会面临一些巨大的挑战和困难,主要有以下一些问题: (1)没有数据缓冲区,I/O性能存在问题...

宸明 ⋅ 04/20 ⋅ 0

做几道基础的Java测试题,看看最近有进步吗?欢迎来学习

Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互...

启示录是真的 ⋅ 05/24 ⋅ 0

2018年Java编程学习面试最全知识点总结

Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互...

Java小辰 ⋅ 05/14 ⋅ 0

甲骨文开源Java 性能监控调试工具 JMC

JMC (Java Mission Control) 是Oracle开源的Java 性能监控调试工具, 源自 JRockit JVM , 主要由三个组件构成:Java 进程浏览器、JMX 控制台和 Java Flight 记录器。 主要特性: Java 进程浏览...

marsdream ⋅ 05/07 ⋅ 0

Java调用Keras、Tensorflow模型

实现python离线训练模型,Java在线预测部署。查看原文 目前深度学习主流使用python训练自己的模型,有非常多的框架提供了能快速搭建神经网络的功能,其中Keras提供了high-level的语法,底层可...

ioiogoo ⋅ 04/03 ⋅ 0

重磅!Java 性能监控调试工具 JMC 宣布开源

JRockit JVM 创始人之一、Oracle Java 产品组成员 Marcus Hirt 昨日在其博客上宣布,Java Mission Control(JMC)的源代码已正式开源。 JMC 是源自 JRockit JVM 的一套监控和管理工具,Oracl...

王练 ⋅ 05/07 ⋅ 6

《成神之路-基础篇》JVM——JVM参数及调优(已完结)

Java内存模型,Java内存管理,Java堆和栈,垃圾回收 本文是[《成神之路系列文章》][1]的第一篇,主要是关于JVM的一些介绍。 持续更新中 JVM参数及调优 JVM实用参数系列 成为Java GC专家(5)...

⋅ 05/05 ⋅ 0

面试中关于Java虚拟机(jvm)的问题看这篇就够了

最近看书的过程中整理了一些面试题,面试题以及答案都在我的文章中有所提到,希望你能在以问题为导向的过程中掌握虚拟机的核心知识。面试毕竟是面试,核心知识我们还是要掌握的,加油~~~ 下面...

snailclimb ⋅ 05/12 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

个人博客的运营模式能否学习TMALL天猫质量为上?

心情随笔|个人博客的运营模式能否学习TMALL天猫质量为上? 中国的互联网已经发展了很多年了,记得在十年前,个人博客十分流行,大量的人都在写博客,而且质量还不错,很多高质量的文章都是在...

原创小博客 ⋅ 今天 ⋅ 0

JavaScript零基础入门——(十一)JavaScript的DOM操作

JavaScript零基础入门——(十一)JavaScript的DOM操作 大家好,欢迎回到我们的JavaScript零基础入门。最近有些同学问我说,我讲的的比书上的精简不少。其实呢,我主要讲的是我在开发中经常会...

JandenMa ⋅ 今天 ⋅ 0

volatile和synchronized的区别

volatile和synchronized的区别 在讲这个之前需要先了解下JMM(Java memory Model :java内存模型):并发过程中如何处理可见性、原子性、有序性的问题--建立JMM模型 详情请看:https://baike.b...

MarinJ_Shao ⋅ 今天 ⋅ 0

深入分析Kubernetes Critical Pod(一)

Author: xidianwangtao@gmail.com 摘要:大家在部署Kubernetes集群AddOn组件的时候,经常会看到Annotation scheduler.alpha.kubernetes.io/critical-pod"="",以表示这是一个关键服务,那你知...

WaltonWang ⋅ 今天 ⋅ 0

原子性 - synchronized关键词

原子性概念 原子性提供了程序的互斥操作,同一时刻只能有一个线程能对某块代码进行操作。 原子性的实现方式 在jdk中,原子性的实现方式主要分为: synchronized:关键词,它依赖于JVM,保证了同...

dotleo ⋅ 今天 ⋅ 0

【2018.06.22学习笔记】【linux高级知识 14.4-15.3】

14.4 exportfs命令 14.5 NFS客户端问题 15.1 FTP介绍 15.2/15.3 使用vsftpd搭建ftp

lgsxp ⋅ 今天 ⋅ 0

JeeSite 4.0 功能权限管理基础(Shiro)

Shiro是Apache的一个开源框架,是一个权限管理的框架,实现用户认证、用户授权等。 只要有用户参与一般都要有权限管理,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户...

ThinkGem ⋅ 昨天 ⋅ 0

python f-string 字符串格式化

主要内容 从Python 3.6开始,f-string是格式化字符串的一种很好的新方法。与其他格式化方式相比,它们不仅更易读,更简洁,不易出错,而且速度更快! 在本文的最后,您将了解如何以及为什么今...

阿豪boy ⋅ 昨天 ⋅ 0

Python实现自动登录站点

如果我们想要实现自动登录,那么我们就需要能够驱动浏览器(比如谷歌浏览器)来实现操作,ChromeDriver 刚好能够帮助我们这一点(非谷歌浏览器的驱动有所不同)。 一、确认软件版本 首先我们...

blackfoxya ⋅ 昨天 ⋅ 0

线性回归原理和实现基本认识

一:介绍 定义:线性回归在假设特证满足线性关系,根据给定的训练数据训练一个模型,并用此模型进行预测。为了了解这个定义,我们先举个简单的例子;我们假设一个线性方程 Y=2x+1, x变量为商...

wangxuwei ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部