• 一同步与异步阻塞与非阻塞

    • 1同步与异步
    • 2阻塞与非阻塞
    • 3IO模式
  • 二BIO
    • 概念描述
    • 特点
    • 代码实现
  • 三NIO
    • 概念描述
    • 特点
    • 代码描述
  • 四AIO

一、同步与异步、阻塞与非阻塞

1、同步与异步

同步与异步的区别在于,数据从内核空间拷贝到用户空间是否由用户线程完成。
– 对于同步来说,分阻塞和非阻塞两种。阻塞的情况,一个线程维护一个链接,该线程完成数据的读写与处理的全部过程,并且数据的读写是阻塞的。 对于非阻塞来说,虽然读写的过程不会阻塞当前线程,立即返回,但是用户线程(Selector选择器)仍然要不断主动去判断数据是否“就绪”(感兴趣的事件是否发生,具体可参考后文对NIO的描述),当出现可以操作的IO时,进行数据的读写并处理,此时还是会阻塞等待内核复制数据到用户进程。他与同步BIO的区别是后者使用一个连接全程等待;
可参考下图(同步非阻塞):

可以看到,在将数据从内核拷贝到用户空间这一过程,是由用户线程阻塞完成的。

–对于异步来说,用户进行读或者写后,将立刻返回,由内核去完成数据读取以及拷贝工作,完成后通知用户,并执行回调函数(用户提供的callback),此时数据已从内核拷贝到用户空间,用户线程只需要对数据进行处理即可,不需要关注读写,用户不需要等待内核对数据的复制操作,用户在得到通知时数据已经被复制到用户空间
可参考下图(异步非阻塞):

可发现,用户在调用之后,立即返回,由内核完成数据的拷贝工作,并通知用户线程,进行回调。

(IO“就绪”和“完成”的区别:就绪指的是还需要用户自己去处理,完成指的是内核帮助完成了,用户不用关心IO过程,只需要提供回调函数。)
(一般来说,IO操作都分为两个阶段,就拿套接口的输入操作来说,它的两个阶段主要是:1)等待网络数据到来,当分组到来时,将其拷贝到内核空间的临时缓冲区中2)将内核空间临时缓冲区中的数据拷贝到用户空间缓冲区中)

(网上还有一种对异步同步的解释也很形象:同步和异步关注的是消息通信机制synchronous communication/ asynchronous communication。所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。)

2、阻塞与非阻塞

阻塞与非阻塞IO,指的是在IO读写过程中有没有阻塞线程,即当没有可读数据时,方法是否即刻返回。
例如在BIO中,使用流的方式进行读写操作,而流的读写操作是阻塞的,例如inpustrem.readline()函数,当没有有效数据可读时,线程将阻塞在该语句处。写操作是同样的道理。如下图所示:

而在NIO中,使用channel与buffer的方式进行数据的读写,只有在有可读或者可写数据时,才会将数据从内核空间读/写入用户空间缓冲区,不会造成线程的阻塞。具体可参考后文对BIO、NIO的描述。如下图所示:

3、IO模式

在Java中,使用socket进行网络通信,IO有如下模式:BIO、NIO、AIO。
分别代表着:同步阻塞IO、同步非阻塞IO、异步非阻塞IO

二、BIO

1 概念描述

指阻塞式IO通信模式。如下图所示为BIO模式示意图:

每建立一个Socket连接时,同时创建一个新线程对该Socket进行单独通信(采用阻塞的方式通信)。这种方式具有很高的响应速度,并且控制起来也很简单,在连接数较少的时候非常有效,但是如果对每一个连接都产生一个线程的无疑是对系统资源的一种浪费,如果连接数较多将会出现资源不足的情况。

2 特点

他有以下两个特点:
(a)使用一个独立的线程维护一个socket连接,随着连接数量的增多,对虚拟机造成一定压力;
(b)使用流来读取数据,流是阻塞的,当没有可读/可写数据时,线程等待,会造成资源的浪费;

3 代码实现

我们使用Socket编程的方式,用代码来体会一下BIO模式通信方式
服务端监听线程:

package study20170324;/*** Created by apple on 17/3/24.*/import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;/*** 开启服务监听线程,当收到连接请求后,开启新的线程进行处理*/
public class ServerThread implements Runnable{@Overridepublic void run() {try {ServerSocket serverSocket = new ServerSocket(Constant.PORT);while (true){Socket socket = serverSocket.accept();new Thread(new ServerProcessThread(socket)).start();//开启新的线程进行连接请求的处理}} catch (IOException e) {e.printStackTrace();}}
}

服务端数据处理线程:

package study20170324;/*** Created by apple on 17/3/24.*/import java.io.*;
import java.net.Socket;/*** 服务端收到连接请求后,处理请求的线程,阻塞式IO*/
public class ServerProcessThread implements Runnable {private Socket socket;public ServerProcessThread(Socket socket){this.socket = socket;}@Overridepublic void run() {//获取客户端的数据,并写回//等待响应try {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));String line = "";String requestStr = "";System.out.println("来自客户端的数据:");while((line = bufferedReader.readLine()) != null){requestStr += line;System.out.println(line);}Writer writer = new OutputStreamWriter(socket.getOutputStream());writer.write("data from server " + requestStr + "\r\n");writer.flush();writer.close();bufferedReader.close();socket.close();} catch (IOException e) {e.printStackTrace();}}
}

客户端处理线程

package study20170324;import java.io.*;
import java.net.Socket;/*** Created by apple on 17/3/24.*//*** 维护客户端socket连接的线程,阻塞式IO*/
public class ClientProcessThread implements Runnable {private Socket socket;public ClientProcessThread(Socket socket){this.socket = socket;}@Overridepublic void run() {//写数据,等待响应,输出响应String requestStr = "data from client \r\n";try {Writer writer = new OutputStreamWriter(socket.getOutputStream());writer.write(requestStr);writer.flush();socket.shutdownOutput();//等待响应BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));String line;System.out.println("来自服务端的响应:");while((line = bufferedReader.readLine()) != null){System.out.println(line);}writer.close();bufferedReader.close();socket.close();} catch (IOException e) {e.printStackTrace();}}
}

常量类:

package study20170324;/*** Created by apple on 17/3/24.*/
public class Constant {public static final String HOST = "127.0.0.1";public static final int PORT = 8080;
}

主运行类:

package study20170324;import java.io.IOException;
import java.net.Socket;/*** Created by apple on 17/3/24.*/
public class ClientMain {public static void main(String[] args) {//开启服务System.out.println("开启服务,监听端口:" + Constant.PORT);new Thread(new ServerThread()).start();//建立一个socket客户端,发起请求System.out.println("客户端,请求连接,并发送数据");try {Socket socket = new Socket(Constant.HOST,Constant.PORT);new Thread(new ClientProcessThread(socket)).start();//开启新的线程处理socket连接} catch (IOException e) {e.printStackTrace();}}
}

最终的运行结果:

三、NIO

1 概念描述

指的是非阻塞式IO通信模式
针对于BIO的两个特点,其实也是两个缺点,Java提供了NIO通信模式的实现。相对于BIO来说,NIO模式即非阻塞IO。服务器端保存一个Socket连接列表,然后对这个列表进行轮询,如果发现某个Socket端口上有数据可读时(读就绪),则调用该socket连接的相应读操作;如果发现某个 Socket端口上有数据可写时(写就绪),则调用该socket连接的相应写操作;如果某个端口的Socket连接已经中断,则调用相应的析构方法关闭该端口。这样能充分利用服务器资源,效率得到了很大提高。Java中使用Selector、Channel、Buffer来实现上述效果,如下图所示:

线程中包含一个Selector对象,他相当于一个通道管理器,可以实现在一个单独线程中处理多个通道的目的,减少线程的创建数量。远程连接对应一个channel,数据的读写通过buffer均在同一个channel中完成,并且数据的读习是非阻塞的。通道创建后需要注册在selector中,同时需要为该通道注册感兴趣事件(客户端连接服务端事件、服务端接收客户端连接事件、读事件、写事件),selector线程需要采用轮训的方式调用selector的select函数,直到所有注册通道中有兴趣的事件发生,则返回,否则一直阻塞。而后循环处理所有就绪的感兴趣事件。以上步骤解决BIO的两个瓶颈:(1)不必对每个连接分别创建线程;(2)数据读写非阻塞
下面对以下三个概念做一个简单介绍,Java NIO由以下三个核心部分组成:

-(a)selector
Selector允许单线程处理多个Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。要使用Selector,得向Selector注册Channel,然后调用他的select方法,这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子入有新连接接进来,数据接收等。
-(b)channel与buffer
基本上,所有的IO在NIO中都从一个Channel开始。Channel有点像流。数据可以从channel读到buffer,也可以从budder写到channel。
channel和buffer有好几种类型。下面是Java NIO中的一些主要channel的实现:
FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel
正如你所看到的,这些通道涵盖了UDP和TCP网络IO,以及文件IO。
以下是Java NIO里关键的buffer实现:
ByteBuffer
CharBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer

具体介绍可参考http://ifeve.com/overview/ 关于Java NIO的系列教程(上述a、b也摘抄于该教程)

2 特点

NIO的特点:
(a)一个线程可以处理多个通道,减少线程创建数量;
(b)读写非阻塞,节约资源:没有可读/可写数据时,不会发生阻塞导致线程资源的浪费

3 代码描述

使用Java代码实现NIO的通信方式
基础类:

package study20170325;import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;/*** Created by apple on 17/3/25.*/
public class NIOBase {// 线程中的通道管理器public Selector selector;public String from,to;//server or clientpublic NIOBase(String from,String to){this.from = from;this.to = to;}/*** 初始化 该线程中的通道管理器Selector*/public void initSelector() throws IOException {this.selector = Selector.open();}/*** 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则循环处理* 这里主要监听连接事件以及读事件*/public void listen() throws IOException {//轮询访问selectwhile(true){//当注册的事件到达时,方法返回;否则将一直阻塞selector.select();//获得selector中选中的项的迭代器,选中的项为注册的事件Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();//循环处理注册事件/*** 一共有四种事件:* 1. 服务端接收客户端连接事件: SelectionKey.OP_ACCEPT* 2. 客户端连接服务端事件:    SelectionKey.OP_CONNECT* 3. 读事件:                SelectionKey.OP_READ* 4. 写事件:                SelectionKey.OP_WRITE*/while(iterator.hasNext()){SelectionKey key = iterator.next();//手动删除已选的key,以防重复处理iterator.remove();//判断事件性质if (key.isAcceptable()){//服务端接收客户端连接事件accept(key);}else if (key.isReadable()){//读事件read(key);}else if (key.isConnectable()) {//客户端连接事件connect(key);}}}}/*** 当监听到读事件后的处理函数* @param key 事件key,可以从key中获取channel,完成事件的处理*/public void read(SelectionKey key) throws IOException {//step1. 得到事件发生的通道SocketChannel socketChannel = (SocketChannel) key.channel();//step2. 创建读取的缓冲区.将数据读取到缓冲区中ByteBuffer byteBuffer = ByteBuffer.allocate(10);int len = socketChannel.read(byteBuffer);String msg = "";byte[] arr = null;while (len > 0){byteBuffer.flip();arr = new byte[len];byteBuffer.get(arr,0,len);msg += new String(arr);byteBuffer.clear();len = socketChannel.read(byteBuffer);}System.out.println(from + " received data from " + to + ":" + msg);//step3. 再将消息回发给客户端if (from.equals("server"))socketChannel.write(ByteBuffer.wrap(new String(" server send some data back to client").getBytes()));}/*** 当监听到服务端接收客户端连接事件后的处理函数* @param key 事件key,可以从key中获取channel,完成事件的处理*/public void accept(SelectionKey key) throws IOException{}/*** 当监听到客户端连接事件后的处理函数* @param key 事件key,可以从key中获取channel,完成事件的处理*/public void connect(SelectionKey key) throws IOException{}
}

服务端线程类:

package study20170325;/*** Created by apple on 17/3/25.*/import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;/*** 采用NIO的方式,开启服务线程* 该线程存在一个Selector,通道管理器,管理所有的channel* step1.服务初始化时,会初始化一个ServerSocektChannel,并注册到Selector中,注册"服务端接收客户端连接"事件** step2.之后开启监听,轮询判断Selector上是否有需要处理的事件,如果有则循环处理;** step2.1 客户端连接事件:在处理的过程中,获取与客户端连接的通道 socketChannel,并注册到Selector中,通过该通道,与客户端进行读写操作** step2.2 读事件,利用读取缓冲区与通道结合进行*/
public class NIOServerThread extends NIOBase implements Runnable{public NIOServerThread(String from, String to) {super(from, to);}//服务端线程中的通道管理器,使用它,可以在同一个线程中管理多个通道@Overridepublic void run() {try {initSelector();//初始化通道管理器SelectorinitServer(Constant.IP,Constant.PORT);//初始化ServerSocketChannel,开启监听listen();//轮询处理Selector选中的事件} catch (IOException e) {e.printStackTrace();}}/*** 获得一个ServerSocket通道,并通过port对其进行初始化* @param port    监听的端口号*/private void initServer(String ip,int port) throws IOException {//step1. 获得一个ServerSocketChannelServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//step2. 初始化工作serverSocketChannel.configureBlocking(false);//设置通道为非阻塞serverSocketChannel.socket().bind(new InetSocketAddress(ip,port));//step3. 将该channel注册到Selector上,并为该通道注册SelectionKey.OP_ACCEPT事件//这样一来,当有"服务端接收客户端连接"事件到达时,selector.select()方法会返回,否则将一直阻塞serverSocketChannel.register(this.selector,SelectionKey.OP_ACCEPT);}/*** 当监听到服务端接收客户端连接事件后的处理函数* @param key 事件key,可以从key中获取channel,完成事件的处理*/@Overridepublic void accept(SelectionKey key) throws IOException {//step1. 获取serverSocketChannelServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();//step2. 获得和客户端连接的socketChannelSocketChannel socketChannel = serverSocketChannel.accept();socketChannel.configureBlocking(false);//设置为非阻塞//step3. 通过socketChannel给客户端发送信息socketChannel.write(ByteBuffer.wrap(new String("server has a connection with client").getBytes()));//step4. 注册该socketChannelsocketChannel.register(selector,SelectionKey.OP_READ);//为了接收客户端的消息,注册读事件}}

客户端线程类:

package study20170325;/*** Created by apple on 17/3/25.*/import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;/*** NIO 客户端线程*/
public class NIOClientThread extends NIOBase implements Runnable{public NIOClientThread(String from, String to) {super(from, to);}@Overridepublic void run() {try {initSelector();//初始化通道管理器initClient(Constant.IP,Constant.PORT);//初始化客户端连接scoketChannellisten();//开始轮询处理事件} catch (IOException e) {e.printStackTrace();}}/*** 获得一个SocketChannel,并对该channel做一些初始化工作,并注册到* @param ip* @param port*/private void initClient(String ip,int port) throws IOException {//step1. 获得一个SocketChannelSocketChannel socketChannel = SocketChannel.open();//step2. 初始化该channelsocketChannel.configureBlocking(false);//设置通道为非阻塞//step3. 客户端连接服务器,其实方法执行并没有实现连接,需要再listen()方法中调用channel.finishConnect()方法才能完成连接socketChannel.connect(new InetSocketAddress(ip,port));//step4. 注册该channel到selector中,并为该通道注册SelectionKey.OP_CONNECT事件和SelectionKey.OP_READ事件socketChannel.register(this.selector,SelectionKey.OP_CONNECT|SelectionKey.OP_READ);}/*** 当监听到客户端连接事件后的处理函数* @param key 事件key,可以从key中获取channel,完成事件的处理*/@Overridepublic void connect(SelectionKey key) throws IOException {super.connect(key);//step1. 获取事件中的channelSocketChannel socketChannel = (SocketChannel) key.channel();//step2. 如果正在连接,则完成连接if (socketChannel.isConnectionPending()){socketChannel.finishConnect();}socketChannel.configureBlocking(false);//将连接设置为非阻塞//step3. 连接后,可以给服务端发送消息socketChannel.write(ByteBuffer.wrap(new String("client send some data to server").getBytes()));}
}

常量类:

package study20170325;/*** Created by apple on 17/3/25.*/
public class Constant {public static final int PORT = 8080;public static final String IP = "127.0.0.1";
}

运行主类:

package study20170325;/*** Created by apple on 17/3/25.*/public class NIOMain {public static void main(String[] args) {new Thread(new NIOServerThread("server","client")).start();try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}new Thread(new NIOClientThread("client","server")).start();}
}

运行结果为:

四、AIO

异步非阻塞IO,与NIO的区别可参考 异步/同步介绍。
可以参考之前对vertx的学习笔记,vertx相当于node在java上的实现,采用的是AIO的模式,即异步非阻塞IO。

【Java IO模式】Java BIO NIO AIO总结相关推荐

  1. Java IO模型:BIO NIO AIO及netty介绍

  2. Java的IO流 ,BIO NIO AIO 的区别?

    目录 1.在了解不同的IO之前先了解:同步与异步,阻塞与非阻塞的区别: 2.BIO NIO AIO 分别代表什么?(面试简答): 3.BIO和NIO.AIO的区别: 4.java中io流的分类: •  ...

  3. Java之IO,BIO,NIO,AIO

    2019独角兽企业重金招聘Python工程师标准>>> 参考文献一 IO基础知识回顾 java的核心库java.io提供了全面的IO接口.包括:文件读写.标准设备输出等.Java中I ...

  4. Java的IO:BIO | NIO | AIO

    原文: http://my.oschina.net/bluesky0leon/blog/132361 BIO | NIO | AIO,本身的描述都是在Java语言的基础上的.而描述IO,我们需要从两个 ...

  5. Java IO(BIO, NIO, AIO) 总结

    文章转载自:JavaGuide 目录 BIO,NIO,AIO 总结 同步与异步 阻塞和非阻塞 1. BIO (Blocking I/O) 1.1 传统 BIO 1.2 伪异步 IO 1.3 代码示例 ...

  6. java io流区别_Java中IO流的分类和BIO,NIO,AIO的区别

    到底什么是IO 我们常说的IO,指的是文件的输入和输出,但是在操作系统层面是如何定义IO的呢?到底什么样的过程可以叫做是一次IO呢? 拿一次磁盘文件读取为例,我们要读取的文件是存储在磁盘上的,我们的目 ...

  7. Java中IO流的分类和BIO,NIO,AIO的区别

    到底什么是IO 我们常说的IO,指的是文件的输入和输出,但是在操作系统层面是如何定义IO的呢?到底什么样的过程可以叫做是一次IO呢? 拿一次磁盘文件读取为例,我们要读取的文件是存储在磁盘上的,我们的目 ...

  8. IO之 java中BIO NIO AIO原理、区别以及应用

    在本篇文章中,我们主要介绍一下java中的BIO NIO AIO,重点是NIO 先说一下同步.异步.阻塞和非阻塞. 简单来讲,同步和异步是针对内核和应用程序之间的交互而言的:阻塞和非阻塞其实是针对进程 ...

  9. JAVA IO : BIO NIO AIO

    JAVA IO : BIO NIO AIO 同步异步.阻塞非阻塞概念 同步与异步 阻塞与非阻塞 IO VS NIO VS AIO 面向流与面向缓冲 阻塞与非阻塞IO BIO.NIO.AIO的JAVA实 ...

最新文章

  1. java http输出,Java HTTP Client输出空JSON
  2. 5.VMware View 4.6安装与部署-安装view agent与模版
  3. 测试基础理论知识(二)
  4. 关于ANTLR的通用库的需求:使用反射来构建元模型
  5. python中view的用法_APIview使用
  6. 久等了!支付宝集五福活动官宣: 今年又有新玩法
  7. 【实验报告】二 网络嗅探与欺骗
  8. JAVA-SWING:生成透明JTable
  9. Linux资源控制-CPU和内存【转】
  10. sqlparser mysql_phpMyAdmin中sql-parser组件的使用
  11. 存储过程与函数的区别
  12. 爱立信、EMC笔试面试
  13. visa卡号生成器 在线_AINLP公众号新增quot;彩虹屁生成器quot;
  14. 使用max函数计算EXCEL个税公式
  15. 了解talkingData
  16. Ubuntu 使用XCB
  17. mariadb-libs 被 mysql-community-libs-compat-8.0.26-1.el7.x86_64 取代
  18. django完成一个可重用注册登录系统
  19. RK3399平台开发系列讲解(应用开发篇)1.12、RTC闹钟唤醒
  20. 引入CSS样式的三种方法

热门文章

  1. 计算机辅助普通话水平测试应试手册,普通话水平测试应试手册
  2. 【Data Science from Scratch 学习笔记】第2章 Python速成(上)
  3. python 中文乱码问题
  4. strrchr()函数
  5. PnetLab模拟器一键汉化教程
  6. 404错误的处理方式及对SEO的影响(更新)
  7. jndi weblogic mysql_在WebLogic新建针对Oracle数据库的JNDI数据源
  8. 你的能力是更适合做微商还是淘宝
  9. 打印机无法获取IP地址备忘录
  10. 深入计算机组成原理(二十)面向流水线的指令设计(上):一心多用的现代CPU