I/O:同步(synchronous)、异步(asynchronous)、阻塞(blocking)、非阻塞(nonblocking)

1、I/O内部机制

出于安全考虑,用户程序(用户态)是没办法直接操作I/O设备进行数据读入或输出的,需要借助操作系统(内核态)提供的API来进行I/O,所以通常我们说I/O其实是通过系统调用来完成的。

程序发起I/O调用时涉及两个阶段,以read为例:

  1. 等待内核态将数据从外设读入并准备好,进入就绪状态 (Waiting for the data to be ready)
  2. 将数据从内核复制到进程中即内核态复制到用户态 (Copying the data from the kernel to the process)

2、同步异步阻塞非阻塞的区别

阻塞、非阻塞(针对系统调用(内核态)而言?):在于发起的I/O调用是否立即返回。阻塞I/O等I/O完成才返回(即等1、2都结束才返回),非阻塞I/O立即返回,此时I/O还没完成(即1立即返回,若没准备好则循环检测直到就绪,就绪后进行阶段2)。

同步、异步(针对用户线程(用户态)而言?):在于调用者(线程)在发起I/O调用后能否继续执行之后的代码或工作

阻塞不一定是同步的,非阻塞也不一定是异步的,反之亦然。

  • 同步阻塞I/O:如JDK I/O(发起I/O调用不立即返回,调用者在I/O完成前也没法进行之后的操作)
  • 同步非阻塞I/O:如JDK NIO(发起I/O调用后立即返回,此后通过循环检查直到I/O就绪才进行I/O操作,操作完了才进行之后的工作,因此是同步的)。
  • 异步I/O:如可以写一个带回调参数的方法,该方法根据String content、String filePath参数将conent写入指定文件,方法内启用新线程进行I/O,完成后调用回调函数通知调用者。至于是阻塞还是非阻塞则看新线程内进行的I/O是阻塞还是非阻塞的。一个异步阻塞I/O的示例如下:

     1 public class FileIO {
     2     public void saveStrToFile(String fileName, String str, IFileIOCallback callback) {
     3         new Thread(new Runnable() {
     4             @Override
     5             public void run() {
     6                 try {
     7                     File file = getExistsFile(fileName);
     8                     writeStrToFile(str, file);
     9                     callback.onResult(true);
    10                 } catch (IOException e) {
    11                     e.printStackTrace();
    12                     callback.onResult(false);
    13                 }
    14             }
    15         }).start();
    16     }
    17 }

    View Code

3、Unix下的五种I/O模型

(详见 IO同步异步阻塞非阻塞 )

  1. 阻塞I/O(blocking IO,属于synchronous,阶段1、2皆阻塞)
  2. 非阻塞I/O(nonblocking IO,属于synchronous,阶段1非阻塞、2阻塞)
    • 对单个I/O请求意义不大,但给I/O多路复用提供了条件,使能在一个线程里处理多个I/O,从而提高并发处理IO能力。如对于很多个socket连接,若连接一个就建立新线程阻塞处理则可能导致线程数很多,就算采用线程池,每个线程一次仍只能处理一个socket,且多线程切换开销大;此时可采用非阻塞I/O在一个线程里同时处理多个连接,通过轮询看哪个socket数据就绪,这其实就是下面的I/O多路复用。
  3. I/O多路复用(IO multiplexing,或称event driven IO,属于synchronous,阶段1非阻塞、2阻塞)
    • 主要就是利用非阻塞I/O提高并发处理能力:用户进程调用select/epoll(阻塞),select/epoll内部不断轮询所负责的所有socket当某个socket有数据到达了就通知用户进程(非阻塞),户进程再调用read操作(阻塞或非阻塞)。优势在于能处理更多连接,在web server中连接数少的情况下用IO multiplexing性能不一定比multi-threading + blocking IO好。
  4. 信号驱动I/O(signal driven IO,属于synchronous,阶段1非阻塞、2阻塞)
    •   与非阻塞I/O类似,调用立即返回,不过在调用时还注册了信号处理函数,当数据就绪时线程收到SIGIO信号,在信号处理函数中调用I/O操作函数处理数据。
  5. 异步I/O(asynchronous IO),此模式下,调用的阶段1、2都由内核完成,不需要用户线程参与。

需要注意的是,与阻塞I/O模型相比,使用非阻塞I/O(或I/O多路复用)模型的主要目的是提高并发处理I/O的能力,后者一般也在I/O请求量大且每个请求读写的数据量较少的场景下才比前者有优势。此外,上述同步I/O模型的阶段2是阻塞的。

总结:

4、Java NIO

Java IO:阻塞、Stream(file stream、socket stream)、面向字节

Java NIO:非阻塞、Channel(file channel、socket channel)、面向Buffer

4.1、基本概念

主要概念:Channel、Buffer、Selector(多路复用器)

  • Selector:JAVA NIO中的多路复用器,配合SelectionKey使用
  • Channel:ServerSocketChannel、SocketChannel、DatagramChannel(UDP)、FileChannel。通道将数据传输给 ByteBuffer 对象或者从 ByteBuffer 对象获取数据进行传输,通道可以是单向( unidirectional)或者双向的( bidirectional)。
  • Buffer:ByteBuffer、MappedByteBuffer(内存映射文件)、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer、CharBuffer。前两者有两种分配方式:从直接内存或从Java堆内存分配,前者是为了解决非直接缓冲区(如通过wrap()函数所创建的被包装的缓冲区)的效率问题而引入直接的

4.2、Buffer

(更多详情参考:http://www.cnblogs.com/leesf456/p/6713741.html)

1、Buffer内部含有四个属性:(0 <= mark <= position <= limit <= capacity)

  • 容量( Capacity):缓冲区能够容纳的数据元素的最大数量,容量在缓冲区创建时被设定,并且永远不能被改变。
  • 上界(Limit):缓冲区现有元素的个数
  • 位置(Position):下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新。
  • 标记(Mark):一个备忘位置。调用 mark( )来设定 mark = postion。调用 reset( )设定 position = mark。标记在设定前是未定义的(undefined)。

初始化一个容量为10的BtyeBuffer,逻辑视图如下(mark未被设定,position初始为0,capacity为10,limit为10):

2、Buffer操作:分配(allocate、allocateDirect、wrap)、read(flip、rewind)、write(clear、compact)、mark(mark、reset)

  • 分配:allocate、allocateDirect操作创建一个缓冲区对象并分配一个私有的空间来储存指定容量大小的数据;wrap创建一个缓冲区对象但是不分配任何空间来储存数据元素,使用所提供的数组作为存储空间来储存缓冲区中的数据,因此在缓冲区的操作会改动数组,反之亦然。
  • 进入读模式:flip(limit设位position、position设为0,mark失效)——只允许单次读因为limit变了、rewind(假定limit已经被正确设置,position设为0,mark失效)——允许多次重复读,因为limit不变
  • 进入写模式:clear(position设为0,limit设为capacity,mark失效)、compact(数据整体移到前面后,limit设为capacity,position设为最后一个元素后面,mark失效)
  • 标记:mark(mark设为position)、reset(position设为mark)

由上可见,如果连续两次调用flip()则缓冲区的大小变为0。

示例:

package cn.edu.buaa.nio;import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;/*** @author zsm* @date 2017年2月21日 下午3:04:27 Java NIO 有以下Buffer类型:ByteBuffer、MappedByteBuffer(内存映射文件)、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer、CharBuffer。前两者有两种分配方式:从直接内存或从Java堆内存分配*/public class T0_BufferDemo {// read:flip,rewind;// write:clear,compact// mark:mark,reset// read from buffer:inChannel.write(buf)、buf.get()// write into buffer:inChannel.read(buf)、buf.put(..)public static void main(String[] args) throws IOException {// TODO Auto-generated method stubRandomAccessFile aFile = new RandomAccessFile("src/cn/edu/buaa/nio/nio-data.txt", "rw");FileChannel inChannel = aFile.getChannel();ByteBuffer buffer = ByteBuffer.allocate(48);// write to bufferint bytesRead = inChannel.read(buffer);// buffer write manner 1buffer.put((byte) 'z');// buffer write manner 2while (bytesRead != -1) {System.out.println("read " + bytesRead);// read from bufferbuffer.flip();// 从写buffer模式切换到读buffer模式while (buffer.hasRemaining()) {byte b = buffer.get();// buffer read manner 1System.out.println((char) b + " " + (0xff & b));}buffer.clear();// 进入写模式inChannel.write(buffer);// buffer read manner 2bytesRead = inChannel.read(buffer);}inChannel.close();aFile.close();}}

View Code

各种类型的Buffer:

 1     ByteBuffer bb = ByteBuffer.wrap(new byte[] { 0, 1, 2, 3, 4, 5, 6, 'a' });
 2
 3         bb.rewind();
 4         System.out.print("Byte Buffer ");
 5         while (bb.hasRemaining())
 6             System.out.print(bb.position() + " -> " + bb.get() + ", ");
 7         System.out.println();
 8
 9         CharBuffer cb = ((ByteBuffer) bb.rewind()).asCharBuffer();
10         System.out.print("Char Buffer ");
11         while (cb.hasRemaining())
12             System.out.print(cb.position() + " -> " + cb.get() + ", ");
13         System.out.println();
14
15         FloatBuffer fb = ((ByteBuffer) bb.rewind()).asFloatBuffer();
16         System.out.print("Float Buffer ");
17         while (fb.hasRemaining())
18             System.out.print(fb.position() + " -> " + fb.get() + ", ");
19         System.out.println();
20
21         IntBuffer ib = ((ByteBuffer) bb.rewind()).asIntBuffer();
22         System.out.print("Int Buffer ");
23         while (ib.hasRemaining())
24             System.out.print(ib.position() + " -> " + ib.get() + ", ");
25         System.out.println();
26
27         LongBuffer lb = ((ByteBuffer) bb.rewind()).asLongBuffer();
28         System.out.print("Long Buffer ");
29         while (lb.hasRemaining())
30             System.out.print(lb.position() + " -> " + lb.get() + ", ");
31         System.out.println();
32
33         ShortBuffer sb = ((ByteBuffer) bb.rewind()).asShortBuffer();
34         System.out.print("Short Buffer ");
35         while (sb.hasRemaining())
36             System.out.print(sb.position() + " -> " + sb.get() + ", ");
37         System.out.println();
38
39         DoubleBuffer db = ((ByteBuffer) bb.rewind()).asDoubleBuffer();
40         System.out.print("Double Buffer ");
41         while (db.hasRemaining())
42             System.out.print(db.position() + " -> " + db.get() + ", ");
43
44 //结果
45 Byte Buffer 0 -> 0, 1 -> 1, 2 -> 2, 3 -> 3, 4 -> 4, 5 -> 5, 6 -> 6, 7 -> 97,
46 Char Buffer 0 -> , 1 -> ȃ, 2 -> Ѕ, 3 -> ١,
47 Float Buffer 0 -> 9.2557E-41, 1 -> 1.5637004E-36,
48 Int Buffer 0 -> 66051, 1 -> 67438177,
49 Long Buffer 0 -> 283686952306273,
50 Short Buffer 0 -> 1, 1 -> 515, 2 -> 1029, 3 -> 1633,
51 Double Buffer 0 -> 1.401599773079337E-309,

View Code

4.3、FileChannel

  • File通道不能直接创建,只能通过在一个打开的RandomAccessFile、FileInputStream或FileOutputStream的对象上调用getChannel( )方法来获取,并且getChannel是线程安全的。
  • FileChannel 位置position()、大小size()是从底层的文件描述符获得的,channel对position的修改底层文件也能看到,反之亦然。示例如下:

     1 RandomAccessFile randomAccessFile = new RandomAccessFile("F:/gps data/2016-11-11 18087 60399647/all 0800-0810_576832.txt", "r");
     2         // Set the file position
     3         randomAccessFile.seek(1000);
     4         // Create a channel from the file
     5         FileChannel fileChannel = randomAccessFile.getChannel();
     6         // This will print "1000"
     7         System.out.println("file pos: " + fileChannel.position());
     8         // Change the position using the RandomAccessFile object
     9         randomAccessFile.seek(500);
    10         // This will print "500"
    11         System.out.println("file pos: " + fileChannel.position());
    12         // Change the position using the FileChannel object
    13         fileChannel.position(200);
    14         // This will print "200"
    15         System.out.println("file pos: " + randomAccessFile.getFilePointer());

    View Code

    当channel进行read、write时,position自动更新,若position达到size()则read返回-1,若write时position超过文件大小则扩展文件大小以容纳新数据。若read、write时指定了position,则不会改变当前的文件position,由于通道的状态无需更新,因此绝对的读和写可能会更加有效率,操作请求可以直接传到本地代码;并且多个线程可以并发访问同一个文件而不会相互产生干扰,因为每次调用都是原子性的( atomic),并不依靠调用之间系统所记住的状态。

  • FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。与Selector一起使用时,Channel必须处于非阻塞模式下,这意味着不能将FileChannel与Selector一起使用。

4.3.1、零拷贝

  NIO中的FileChannel拥有transferTo和transferFrom两个方法,可直接把FileChannel中的数据拷贝到另外一个Channel,或直接把另外一个Channel中的数据拷贝到FileChannel。该接口常被用于高效的网络/文件的数据传输和大文件拷贝。在操作系统支持的情况下,通过该方法传输数据并不需要将源数据从内核态拷贝到用户态,再从用户态拷贝到目标通道的内核态,同时也避免了两次用户态和内核态间的上下文切换,也即使用了“零拷贝”,所以其性能一般高于Java IO中提供的方法。
详见 Java零拷贝-MarchOn

4.3.2、内存映射文件

  新的 FileChannel 类提供了一个名为 map( )的方法,该方法可以在一个打开的文件和一个特殊类型的 ByteBuffer 之间建立一个虚拟内存映射,由 map( )方法返回的 MappedByteBuffer 对象的行为类似与基于内存的缓冲区,只不过该对象的数据元素存储在磁盘上的文件中。通过内存映射机制来访问一个文件会比使用常规方法读写高效得多,甚至比使用通道的效率都高。

映射方法: buffer = fileChannel.map(MapMode.READ_WRITE, 0, fileChannel.size());

  • 映射模式:MapMode.READ_WRITE、MapMode.READ_ONLY、MapMode.PRIVATE
  • 请求的映射模式将受被调用 map( )方法的 FileChannel 对象的访问权限所限制。如:若通道以只读的权限打开的却请求 MapMode.READ_WRITE 模式,则map( )方法会抛出一个 NonWritableChannelException 异常
  • MapMode.PRIVATE模式表示一个写时拷贝( copy-on-write)的映射,这意味着通过 put( )方法所做的任何修改都会导致产生一个私有的数据拷贝并且该拷贝中的数据只有MappedByteBuffer 实例可以看到。该过程不会对底层文件做任何修改,而且一旦缓冲区被施以垃圾收集动作( garbage collected),那些修改都会丢失。

详见 Java内存映射文件-MarchOn

4.4、SocketChannel、ServerSocketChannel

服务端客户端模型演变:阻塞IO(单线程处理所有请求->每个请求创建一个线程处理[1]->线程池处理所有请求[2])->NIO Reactor模式[3]

[1][2][3]优劣:

  1. 优势:实现非常简单,在小规模环境中能完美工作;
    劣势:在高并发大规模环境下基本无法工作,因为线程的创建和销毁都需要额外的时间开销,另外每创建一个线程都需要一定的系统资源,系统资源有限,不可能无限创建线程,再者,线程数多了之后系统用于上下文切换的时间就会增大;

  2. 优势:实现比较简单,在一般规模的环境中能够很好地工作;
    劣势:在高并发大规模的环境下很可能会因为处理某些任务时需要等待一些外部资源而导致处理时间很长,最终导致整个线程池的所有线程全部繁忙,无法对外提供服务,给用户的感觉就是网站挂了;

  3. 优势:在高并发大规模环境下也能工作得很好,性能很棒;
    劣势:实现比较复杂,Java NIO有该模型的实现

DatagramChannel 和 SocketChannel 实现定义读和写功能的接口而 ServerSocketChannel不实现, ServerSocketChannel 负责监听传入的连接和创建新的 SocketChannel 对象,它本身从不传输数据。

SocketChannel是线程安全的。并发访问时无需特别措施来保护发起访问的多个线程,不过任何时候都只有一个线程的读写操作在进行中。

  • Java NIO可以实现阻塞IO的功能,也可以实现非阻塞IO的功能,后者与Selector结合时为Reactor模式(即I/O多路复用模型)
  • NIO Channel也可以不结合Selector实现服务端客户端,但当与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。

SelectionKey包含属性:

  • interest集合:SelectionKey.OP_CONNECT、OP_ACCEPT、OP_READ、OP_WRITE
  • ready集合:selectionKey.readyOps() ; (isAcceptable()、isConnectable()、isReadable()、isWritable())
  • Channel:selectionKey.channel();
  • Selector:selectionKey.selector();
  • 附加的对象(可选):selectionKey.attach(theObject);  Object attachedObj = selectionKey.attachment();

示例:

0、不结合Selector

 1 //服务端
 2 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
 3         serverSocketChannel.configureBlocking(true);
 4         serverSocketChannel.bind(new InetSocketAddress("localhost", 1234));
 5         while (true) {
 6             SocketChannel socketChannel = serverSocketChannel.accept();
 7             if (socketChannel != null) {
 8                 System.out.println(socketChannel.getRemoteAddress());
 9             } else {
10                 System.out.println("no connect");
11             }
12         }

View Code

1、简单Reactor模式(一线程处理连接、监听就绪及读写操作)

 1 package cn.edu.buaa.nio;
 2
 3 import java.io.IOException;
 4 import java.net.InetSocketAddress;
 5 import java.nio.ByteBuffer;
 6 import java.nio.channels.SelectionKey;
 7 import java.nio.channels.Selector;
 8 import java.nio.channels.ServerSocketChannel;
 9 import java.nio.channels.SocketChannel;
10 import java.util.Iterator;
11 import java.util.Scanner;
12 import java.util.Set;
13
14 /**
15  * @author zsm
16  * @date 2017年3月14日 上午10:32:57<br>
17  *
18  */
19 //Java NIO的选择器允许一个单独的线程同时监视多个通道,可以注册多个通道到同一个选择器上,然后使用一个单独的线程来“选择”已经就绪的通道。这种“选择”机制为一个单独线程管理多个通道提供了可能。
20 //http://www.jasongj.com/java/nio_reactor/#精典Reactor模式
21 /**
22  * 单线程Reactor模式<br>
23  * 多个Channel可以注册到同一个Selector对象上,实现了一个线程同时监控多个请求状态(Channel)。同时注册时需要指定它所关注的事件,例如上示代码中socketServerChannel对象只注册了OP_ACCEPT事件,而socketChannel对象只注册了OP_READ事件。
24  */
25 public class T3_ReactorDemo1_NIOServer {
26     // 与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。
27     public static void main(String[] args) throws IOException {
28         Selector selector = Selector.open();
29         ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
30         serverSocketChannel.configureBlocking(false);
31         serverSocketChannel.bind(new InetSocketAddress(1234));
32         serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
33         while (selector.select() > 0) {
34             Set<SelectionKey> keys = selector.selectedKeys();
35             Iterator<SelectionKey> iterator = keys.iterator();
36             while (iterator.hasNext()) {
37                 SelectionKey key = iterator.next();
38                 iterator.remove();
39                 if (key.isAcceptable()) {
40                     ServerSocketChannel acceptServerSocketChannel = (ServerSocketChannel) key.channel();
41                     SocketChannel socketChannel = acceptServerSocketChannel.accept();
42                     socketChannel.configureBlocking(false);
43                     System.out.println("Accept request from " + socketChannel.getRemoteAddress());
44                     socketChannel.register(selector, SelectionKey.OP_READ);
45                 } else if (key.isReadable()) {
46                     SocketChannel socketChannel = (SocketChannel) key.channel();
47                     ByteBuffer buffer = ByteBuffer.allocate(1024);
48                     int count = socketChannel.read(buffer);// 内核态数据复制到用户态,阻塞
49                     if (count <= 0) {
50                         socketChannel.close();
51                         key.cancel();
52                         System.out.println("Received invalide data, close the connection");
53                         continue;
54                     }
55                     System.out.println("Received message " + new String(buffer.array()));
56                 }
57                 keys.remove(key);
58             }
59         }
60     }
61 }
62
63 class T3_ReactorDemo1_NIOClient {
64     public static void main(String[] args) throws IOException {
65         SocketChannel socketChannel = SocketChannel.open();
66         socketChannel.connect(new InetSocketAddress(1234));
67         ByteBuffer buffer = ByteBuffer.allocate(1024);
68         Scanner scanner = new Scanner(System.in);
69         String tmpStr;
70         while ((tmpStr = scanner.nextLine()) != null) {
71             buffer.clear();
72             buffer.put(tmpStr.getBytes());
73             buffer.flip();
74             while (buffer.hasRemaining()) {
75                 socketChannel.write(buffer);
76             }
77         }
78     }
79 }

View Code

2、多线程Reactor模式(一线程处理连接、监听就绪工作,多线程处理读写操作)

 1 package cn.edu.buaa.nio;
 2
 3 import java.io.IOException;
 4 import java.net.InetSocketAddress;
 5 import java.nio.ByteBuffer;
 6 import java.nio.channels.SelectionKey;
 7 import java.nio.channels.Selector;
 8 import java.nio.channels.ServerSocketChannel;
 9 import java.nio.channels.SocketChannel;
10 import java.util.Iterator;
11 import java.util.Scanner;
12 import java.util.Set;
13 import java.util.concurrent.ExecutorService;
14 import java.util.concurrent.Executors;
15
16 /**
17  * @author zsm
18  * @date 2017年3月14日 上午10:45:05
19  */
20 // http://www.jasongj.com/java/nio_reactor/#多工作线程Reactor模式
21 /**
22  * 多线程Reactor模式<br>
23  * 经典Reactor模式中,尽管一个线程可同时监控多个请求(Channel),但是所有读/写请求以及对新连接请求的处理都在同一个线程中处理,无法充分利用多CPU的优势,同时读/写操作也会阻塞对新连接请求的处理。因此可以引入多线程,并行处理多个读/写操作
24  */
25 public class T3_ReactorDemo2_NIOServer {
26     // 与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。
27     public static void main(String[] args) throws IOException {
28         Selector selector = Selector.open();
29         ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
30         serverSocketChannel.configureBlocking(false);
31         serverSocketChannel.bind(new InetSocketAddress(1234));
32         serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
33         while (true) {
34             if (selector.selectNow() < 0) {
35                 continue;
36             }
37             Set<SelectionKey> keys = selector.selectedKeys();
38             Iterator<SelectionKey> iterator = keys.iterator();
39             while (iterator.hasNext()) {
40                 SelectionKey key = iterator.next();
41                 iterator.remove();
42                 if (key.isAcceptable()) {
43                     ServerSocketChannel acceptServerSocketChannel = (ServerSocketChannel) key.channel();
44                     SocketChannel socketChannel = acceptServerSocketChannel.accept();
45                     socketChannel.configureBlocking(false);
46                     System.out.println("Accept request from " + socketChannel.getRemoteAddress());
47                     SelectionKey readKey = socketChannel.register(selector, SelectionKey.OP_READ);
48                     readKey.attach(new Processor1());
49                 } else if (key.isReadable()) {
50                     Processor1 processor = (Processor1) key.attachment();
51                     processor.process(key);
52                 }
53             }
54         }
55     }
56 }
57
58 class Processor1 {
59     private static final ExecutorService service = Executors.newFixedThreadPool(16);
60
61     public void process(SelectionKey selectionKey) {
62         service.submit(() -> {
63             ByteBuffer buffer = ByteBuffer.allocate(1024);
64             SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
65             int count = socketChannel.read(buffer);// 内核态数据复制到用户态,阻塞
66             if (count < 0) {
67                 socketChannel.close();
68                 selectionKey.cancel();
69                 System.out.println(socketChannel + "\t Read ended");
70                 return null;
71             } else if (count == 0) {
72                 return null;
73             }
74             System.out.println(socketChannel + "\t Read message " + new String(buffer.array()));
75             return null;
76         });
77     }
78 }
79
80 class T3_ReactorDemo2_NIOClient {
81     public static void main(String[] args) throws IOException {
82         SocketChannel socketChannel = SocketChannel.open();
83         socketChannel.connect(new InetSocketAddress(1234));
84         ByteBuffer buffer = ByteBuffer.allocate(1024);
85         Scanner scanner = new Scanner(System.in);
86         String tmpStr;
87         while ((tmpStr = scanner.nextLine()) != null) {
88             buffer.clear();
89             buffer.put(tmpStr.getBytes());
90             buffer.flip();
91             while (buffer.hasRemaining()) {
92                 socketChannel.write(buffer);
93             }
94         }
95     }
96 }

View Code

3、多Reactor模式(一线程处理连接工作,多线程处理监听就绪及读写操作)

  1 package cn.edu.buaa.nio;
  2
  3 import java.io.IOException;
  4 import java.net.InetSocketAddress;
  5 import java.nio.ByteBuffer;
  6 import java.nio.channels.ClosedChannelException;
  7 import java.nio.channels.SelectionKey;
  8 import java.nio.channels.Selector;
  9 import java.nio.channels.ServerSocketChannel;
 10 import java.nio.channels.SocketChannel;
 11 import java.nio.channels.spi.SelectorProvider;
 12 import java.util.Iterator;
 13 import java.util.Scanner;
 14 import java.util.Set;
 15 import java.util.concurrent.ExecutorService;
 16 import java.util.concurrent.Executors;
 17
 18 /**
 19 * @author  zsm
 20 * @date 2017年3月14日 上午10:55:01
 21 */
 22 //http://www.jasongj.com/java/nio_reactor/#多Reactor
 23 /**
 24  * 多Reactor模式<br>
 25  * Netty中使用的Reactor模式,引入了多Reactor,也即一个主Reactor负责监控所有的连接请求,多个子Reactor负责监控并处理读/写请求,减轻了主Reactor的压力,降低了主Reactor压力太大而造成的延迟。
 26  * 并且每个子Reactor分别属于一个独立的线程,每个成功连接后的Channel的所有操作由同一个线程处理。这样保证了同一请求的所有状态和上下文在同一个线程中,避免了不必要的上下文切换,同时也方便了监控请求响应状态。
 27  */
 28 public class T3_ReactorDemo3_NIOServer {
 29     // 与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。
 30     public static void main(String[] args) throws IOException {
 31         Selector selector = Selector.open();
 32         ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
 33         serverSocketChannel.configureBlocking(false);
 34         serverSocketChannel.bind(new InetSocketAddress(1234));
 35         serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
 36         int coreNum = Runtime.getRuntime().availableProcessors();
 37         Processor2[] processors = new Processor2[coreNum];
 38         for (int i = 0; i < processors.length; i++) {
 39             processors[i] = new Processor2();
 40         }
 41         int index = 0;
 42         while (selector.select() > 0) {
 43             Set<SelectionKey> keys = selector.selectedKeys();
 44             for (SelectionKey key : keys) {
 45                 keys.remove(key);
 46                 if (key.isAcceptable()) {
 47                     ServerSocketChannel acceptServerSocketChannel = (ServerSocketChannel) key.channel();
 48                     SocketChannel socketChannel = acceptServerSocketChannel.accept();
 49                     socketChannel.configureBlocking(false);
 50                     System.out.println("Accept request from " + socketChannel.getRemoteAddress());
 51                     Processor2 processor = processors[(index++) / coreNum];
 52                     processor.addChannel(socketChannel);
 53                 }
 54             }
 55         }
 56     }
 57 }
 58
 59 class Processor2 {
 60     private static final ExecutorService service = Executors
 61             .newFixedThreadPool(2 * Runtime.getRuntime().availableProcessors());
 62     private Selector selector;
 63
 64     public Processor2() throws IOException {
 65         this.selector = SelectorProvider.provider().openSelector();
 66         start();
 67     }
 68
 69     public void addChannel(SocketChannel socketChannel) throws ClosedChannelException {
 70         socketChannel.register(this.selector, SelectionKey.OP_READ);
 71     }
 72
 73     public void start() {
 74         service.submit(() -> {
 75             while (true) {
 76                 if (selector.selectNow() <= 0) {
 77                     continue;
 78                 }
 79                 Set<SelectionKey> keys = selector.selectedKeys();
 80                 Iterator<SelectionKey> iterator = keys.iterator();
 81                 while (iterator.hasNext()) {
 82                     SelectionKey key = iterator.next();
 83                     iterator.remove();
 84                     if (key.isReadable()) {
 85                         ByteBuffer buffer = ByteBuffer.allocate(1024);
 86                         SocketChannel socketChannel = (SocketChannel) key.channel();
 87                         int count = socketChannel.read(buffer);// 内核态数据复制到用户态,阻塞
 88                         if (count < 0) {
 89                             socketChannel.close();
 90                             key.cancel();
 91                             System.out.println(socketChannel + "\t Read ended");
 92                             continue;
 93                         } else if (count == 0) {
 94                             System.out.println(socketChannel + "\t Message size is 0");
 95                             continue;
 96                         } else {
 97                             System.out.println(socketChannel + "\t Read message" + new String(buffer.array()));
 98                         }
 99                     }
100                 }
101             }
102         });
103     }
104 }
105
106 class T3_ReactorDemo3_NIOClient {
107     public static void main(String[] args) throws IOException {
108         SocketChannel socketChannel = SocketChannel.open();
109         socketChannel.connect(new InetSocketAddress(1234));
110         ByteBuffer buffer = ByteBuffer.allocate(1024);
111         Scanner scanner = new Scanner(System.in);
112         String tmpStr;
113         while ((tmpStr = scanner.nextLine()) != null) {
114             buffer.clear();
115             buffer.put(tmpStr.getBytes());
116             buffer.flip();
117             while (buffer.hasRemaining()) {
118                 socketChannel.write(buffer);
119             }
120         }
121     }
122 }

View Code

总结:

JAVA NIO使能用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂,且提供的API使用起来略复杂,实际项目中不建议直接使用它们进行开发,而是用Netty等第三方库。

5、Netty

  Netty是JBOSS针对网络开发的一套应用框架,它也是在NIO的基础上发展起来的。netty基于异步的事件驱动,具有高性能、高扩展性等特性,它提供了统一的底层协议接口,使得开发者从底层的网络协议(比如 TCP/IP、UDP)中解脱出来。

  详见: Netty使用示例

6、参考资料

1、http://blog.csdn.net/historyasamirror/article/details/5778378 ——阻塞非阻塞同步异步

2、https://my.oschina.net/andylucc/blog/614295 ——阻塞非阻塞同步异步

3、http://www.jasongj.com/java/nio_reactor/ ——Java NIO Server/Client

4、http://www.iteye.com/magazines/132-Java-NIO ——Java NIO API较详细介绍

5、http://www.cnblogs.com/leesf456/p/6713741.html——Buffer

6、http://www.cnblogs.com/leesf456/p/6715740.html——Channel

转载于:https://www.cnblogs.com/z-sm/p/6680141.html

I/O模型(同步、非同步、阻塞、非阻塞)总结相关推荐

  1. Windows I/O模型、同步/异步、阻塞/非阻塞

    同步 所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回.按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等).但是一般而言,我们在说同步.异步的时候,特指 ...

  2. 面试必会系列 - 5.1 网络BIO、NIO、epoll,同步/异步模型、阻塞/非阻塞模型,你能分清吗?

    本文已收录至 Github(MD-Notes),若博客中图片模糊或打不开,可以来我的 Github 仓库,包含了完整图文:https://github.com/HanquanHq/MD-Notes,涵 ...

  3. Java网络编程------IO模型的同步/异步/阻塞/非阻塞(1)

    IO模型的同步/异步/阻塞/非阻塞 一.同步/异步/阻塞/非阻塞 1.同步和异步 2.阻塞和非阻塞 3.同步.异步和阻塞.非阻塞组合 二.IO 1.I/O 2.阻塞IO和非阻塞IO 3.同步IO和同步 ...

  4. 异步通知是什么意思_一次相亲经历,我彻底搞懂了阻塞非阻塞、同步异步

    看到标题,可能你会想,相亲跟阻塞/非阻塞,同步/异步有啥关系,这个逗b不知道在想什么东西.不要急,且听我慢慢道来 年纪大了,一回家七大姑八大姨就各种催婚,都说要给我介绍女朋友.这不,刚刚门口,我的大姨 ...

  5. NIO详解(二): BIO 浅谈 同步 异步与阻塞 非阻塞

    在我们了解Java NIO/BIO的网络通信之前,我们先了解一下常用的阻塞/非阻塞模型以及同步/异步的概念 一.阻塞和非阻塞 从简单的开始,我们以经典的读取文件的模型举例.(对操作系统而言,所有的输入 ...

  6. 异步/同步、阻塞/非阻塞的理解

    异步/同步.阻塞/非阻塞的理解 [同步和异步] 通俗的讲: 同步是指:发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式.  异步是指:发送方发出数据后,不等接收方发回响应,接着发送下个 ...

  7. 异步和同步区别是什么_一次相亲经历,我彻底搞懂了什么叫阻塞非阻塞,同步异步...

    "看到标题,可能你会想,相亲跟阻塞/非阻塞,同步/异步有啥关系,这个逗b不知道在想什么东西.不要急,且听我慢慢道来 年纪大了,一回家七大姑八大姨就各种催婚,都说要给我介绍女朋友.这不,刚刚门 ...

  8. 同步异步阻塞非阻塞杂记

    版权声明:本文可能为博主原创文章,若标明出处可随便转载. https://blog.csdn.net/Jailman/article/details/78498458 gevent实现的协程是同步非阻 ...

  9. 怎样理解阻塞非阻塞与同步异步的区别?

    发现很多人对这两个概念往往混为一谈(包括本人,不是很理解). 阻塞"与"非阻塞"与"同步"与"异步"不能简单的从字面理解,提供一个 ...

  10. 确定不来了解一下什么是 BIO NIO AIO 阻塞 非阻塞 同步 异步?

    本文内容涉及同步与异步, 阻塞与非阻塞, BIO.NIO.AIO等概念, 这块内容本身比较复杂, 很难用三言两语说明白. 而书上的定义更不容易理解是什么意思. 下面跟着我一起解开它们神秘的面纱. BI ...

最新文章

  1. 你花了多久弄明白架构设计?java多线程编程实战指南pdf
  2. 【semantic】本体和语义网的研究方向
  3. Spring-属性文件自身的引用03
  4. 【KVM系列06】Nova 通过 libvirt 管理 QEMU/KVM 虚机
  5. System Monitor ArcGIS系统监控利器
  6. Spring Cloud中Feign如何统一设置验证token
  7. 第十二章 类和动态内存分配
  8. Codeforces 1480B. The Great Hero(阅读模拟题,注意数据范围和攻击顺序)
  9. Javascript是实现HTML5强大功能的重要语言
  10. 2021年中国中级订单选择器(3至8+m)市场趋势报告、技术动态创新及2027年市场预测
  11. HDU 5934 2016CCPC杭州 B: Bomb(Trajan强连通)
  12. 联想a500手机驱动_一块砖也敢刷:联想手机A368T刷了三次才重新进入系统
  13. Servlet(四):转发与重定向、路径问题
  14. 抖音地球html代码,抖音短视频征服海外 1/6的地球移动网民活跃
  15. 利用wireshark分析Voip语音RTP协议
  16. P18利用5次shift漏洞破解win7密码
  17. SpringBoot的使用01
  18. cf/codeforces #365 E - Mishka and Divisors 数学+背包dp+gcd
  19. 自定义注解:具体的设计作用一般看过滤器的实现(以@Secured为例子部分理解)
  20. 怎么把PS界面语言变成英文方法教程

热门文章

  1. Pytest全栈自动化测试指南-入门
  2. CentOS7.4安装ClamAV反病毒软件
  3. 华为鸿蒙手机用大卡还是小卡,【荣耀3XPro评测】大卡+小卡双3G网络-中关村在线...
  4. matlab由方波转换为梯形波,matlab怎样将方波转换为二进制数据
  5. 软件测试教学实训平台
  6. bzoj2101:[USACO2010 DEC]TREASURE CHEST 藏宝箱
  7. linux 下载文件
  8. python使用selenium模拟浏览器进入好友QQ空间留言
  9. 大数据分析:将大数据转化为巨额资金 第2章和第3章
  10. 特朗普Twitter账号解封!马斯克:人民的声音,上帝的声音