同步和异步是针对应用程序和内核的交互而言的,主要看是主动轮训内核还是等待内核通知。 阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。
阻塞:当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待 状态, 直到有东西可读或者可写为止。餐厅窗口点餐,当说明我们需要的餐品之后,窗口只会在加工完成并且送出我们需要的餐品的时候才会响应我们,在这期间,不可以做其他的事情,我们必须保持等待。
非阻塞:当试图对该文件描述符进行读写时, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待。餐厅窗口点餐,当说明我们需要的餐品之后,窗口立刻响应,给我们一张小票,领票完后我们自己可以玩玩手机,或者与别人聊聊天,我们可以轮询(同步)我们的餐品是否OK或者等待通知(异步)。
IO | NIO | AIO(NIO2) |
---|---|---|
面向流 | 面向缓冲 | 面向缓冲 |
阻塞IO | 非阻塞IO | 非阻塞IO |
同步 | 同步 | 异步 |
无 | 选择器 | 观察者 |
Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
Java IO的各种流是阻塞的。这意味着,当一个线程调用read()
或 write()
时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO
的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
在IO设计中,我们从InputStream或 Reader逐字节读取数据。假设你正在处理一基于行的文本数据流,例如:
Name: Anna
Age: 25
Email: anna@mailserver.com
Phone: 1234567890
该文本行的流可以这样处理:
InputStream input = null;
try {
input = new FileInputStream("/data/file/temp/test.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
try {
//堵塞到读取本行数据完成
String nameLine = reader.readLine();
//堵塞到读取本行数据完成
String ageLine = reader.readLine();
//堵塞到读取本行数据完成
String emailLine = reader.readLine();
//堵塞到读取本行数据完成
String phoneLine = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
一旦reader.readLine()
方法返回,你就知道肯定文本行就已读完, readline()
阻塞直到整行读完,这就是原因。你也知道此行包含名称;同样,第二个readline()
调用返回的时候,你知道这行包含年龄等。 正如你可以看到,该处理程序仅在有新数据读入时运行,并知道每步的数据是什么。一旦正在运行的线程已处理过读入的某些数据,该线程不会再回退数据(大多如此)。
而一个NIO的实现会有所不同,下面是一个简单的例子:
RandomAccessFile aFile = null;
try {
aFile = new RandomAccessFile("/data/file/temp/big.txt", "rw");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
FileChannel inChannel = aFile.getChannel();
//建造一块20480字节的大缓冲区
ByteBuffer buf = ByteBuffer.allocate(20480);
//写入文件信息到缓冲区
int bytesRead = 0;
try {
//不会堵塞,立刻返回
bytesRead = inChannel.read(buf);
//查看已经读取到缓存的字节数
System.out.println(bytesRead);
while (bytesRead != -1) {
//转为读取模式
buf.flip();
while(buf.hasRemaining()){
// 一次读取一个字节
System.out.print((char) buf.get());
}
//清空缓冲区
buf.clear();
//继续写入文件信息到缓冲区
bytesRead = inChannel.read(buf);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
aFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
从通道读取字节到ByteBuffer。当这个方法调用返回时,你不知道你所需的所有数据是否全部加载完成是否都已经在缓冲区内。
结果如下
另外,I/O属于底层操作,需要操作系统支持,并发也需要操作系统的支持,所以性能方面不同操作系统差异会比较明显。