IO
自己的思考
1、NIO使用复杂,合理使用能在性能上有很大的提升
2、一直顺序的读写一个文件,BIO可能比NIO效率更高。NIO主要应用于大吞吐量和系统负载比较高的时候(针对于阻塞和非阻塞),快速读取和写入要采取内存映射文件,网络编程也要采用epoll,变更通知也要使用NIO。
NIO不能说比BIO好,两者只是功能的互补,不同的场景下应该选择不同的IO。
3、BIO
1、面向字节流的
支持的种类:ByteArray、StringBuffer、File、Pipe、Sequence、Filter
重要的装饰器:
DataXXStream:通过DataXXStream可以使用更高级的数据类型,如:writeUTF等
BufferedInputStream:缓冲作用
2、面向字符流的
支持的类型:File、String、CharArray、Pipe
Reader和Writer是面向字符的,在IO操作时对外方法不能指定编码,只能使用JVM默认的编码。当需要指定编码的时候需要通过OutputStreamWriter和InputStreamReader这两个适配器结合面向 字节的IO共同使用。
4、NIO1.0
开放了更多的操作系统底层的系统调用
1、面向channel
管道可以使双向的
2、缓冲区
写IO的时候可能会引起磁盘的操作也可能不会引起磁盘的操作,磁盘操作是很昂贵的工作,所以进行写IO时一定要一次写大量的数据才会更高效。所以Netty的写socket实际上是先写到ByteBuffer里
面,在flush的时候再不断的循环写socket(16次),这里使用的就是ByteBuffer缓冲数据。
IO读是非阻塞的,所以可能 需要不断的循环读数据,读的数据也是不断的存储在缓冲区里面的。
2、非阻塞
BIO是阻塞的,在进行IO操作时如果此时IO设备很忙,该线程将会Block很久(除非关闭IO引起IO Exception才能从IO操作那行语句返回,否则操作IO的语句是不能中断的)。采取非阻塞的方式 之
后 ,程序不是一直Block,如果程序感知到IO设备很忙,则可以进行其他操作,而不是一直Block,比如:在循环进行IO操作n次后IO还未完成就终止程序提示IO忙。非阻塞主要是程序不是一直Block,程序自己有控制权。
3、内存映射文件
虚拟内存(缺页的中断和调度)
4、IO多路复用
4、NIO2.0
真正的异步非阻塞
变更通知(watcher机制 )
5、同步和异步
在多进程、线程和多机器的应用中,经常出现调用,调用分为同步和异步。下面就分析一下同步和异步的区别和优劣
1、同步
同步就是A调B去完成一件事情,B直到完成了才会返回结果给A,此时A会阻塞直到调用超时。在这种场景里,A的线程会一直处于阻塞状态,当流量比较大时,A会因为各个线程因为同步调用阻塞时间较长,而导致线程飙升和系统压力大。系统在吞吐量上会有瓶 颈,而且在大流量下容易挂机。
优点:编程简单粗暴,逻辑简单
除非因为超时原因,A在收到返回之后就能知道B是否成功完成了任务
缺点:A在调用过程中,一般只能阻塞,该线程对自己没有控制能力了。
A的吞吐量会降低,同时系统能支撑的负载也交低。
A和B不能做到并行处理
2、异步
异步就是A调用B,A只是通知B去调用,B收到通知之后立刻返回A已经收到通知,然后B再处理。此时A线程不会因为B的任务处理时间长而造成很长时间的阻塞,A收到回执后可以处理其他事情,此时A对自己的线程有绝对的控制能力。同时A和B可以做到同时并行 处理。
此时A有两种办法知道B的完成情况:1、B完成之后会主动通知A,比如回调。 2、A主动去查询B的执行情况,例如通过future去主动查询B的完成情况。
优点:A和B能真正做到并行。
A能对自己的线程有很强的控制能力。
A系统的吞吐量会很高,同时系统能支撑的负载也很高。
缺点:编程模型复杂
需要通过回调通知或者主动回查B的执行情况。
3、为什么设计系统的时候要异步化
1、异步能提供系统的并行能力
2、请求方和目标方进行了隔离。异步使得请求线程不会因为处理方的处理时间很长而一直阻塞,此时请求线程对自己拥有绝对的控制能力。
比如:MQ,生产者-消费者模型等。异步在一定程度上也可以进行解耦。
3、异步的被调用方一般会提供查询状态服务或者进行回调机制来使得被调用方知道任务是否完成。
1、被调用方提供状态查询服务 只是起到了隔离作用(和非阻塞类似,调用方需要不断的浪费cpu去轮询任务状态)
2、提供回调机制 1、起到了隔离的作用 2、不用浪费cpu和线程去轮询任务状态,而且能够及时的得到任务状态
4、调用方法时一定要不能无限期的阻塞,一般有以下几种方法:
1、future.get(time)
2、非阻塞
3、异步花方案
5、软件系统的精确性和系统的吞吐量,能承受的负载压力是相违背的,一般有时从精确性换取系统的性能。如:流控等保障机制
变更通知(因为每个事件都需要一个监听者)
对NIO和NIO.2有兴趣的开发者的共同关注点在于Java应用的性能。根据我的经验,NIO.2里的文件变更通知者(file change notifier)是新输入/输出API里最让人感兴趣(被低估了)的特性。
很多企业级应用需要在下面的情况时做一些特殊的处理:
- 当一个文件上传到一个FTP文件夹里时
- 当一个配置里的定义被修改时
- 当一个草稿文档被上传时
- 其他的文件系统事件出现时
这些都是变更通知或者变更响应的例子。在Java(以及其他语言)的早期版本里,轮询(polling)是检测这些变更事件的最好方式。轮询是一种特殊的无限循环:检查文件系统或者其他对象,并且和之前的状态对比,如果没有变化,在大概几百个毫秒或者10秒的间隔后,继续检查。就这一直无限循环下去。
NIO.2提供了一个更好地方式来进行变更检测。列表1是一个简单的示例。
列表1. NIO.2里的变更通知机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
import java.nio.file.attribute.*; import java.io.*; import java.util.*; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.List; public class Watcher { public static void main(String[] args) { Path this_dir = Paths.get( "." ); System.out.println( "Now watching the current directory ..." ); try { WatchService watcher = this_dir.getFileSystem().newWatchService(); this_dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE); WatchKey watckKey = watcher.take(); List<WatchEvent<< 64 ;>> events = watckKey.pollEvents(); for (WatchEvent event : events) { System.out.println( "Someone just created the file '" + event.context().toString() + "'." ); } } catch (Exception e) { System.out.println( "Error: " + e.toString()); } } } |
编译这段代码,然后在命令行里执行。在相同的目录下,创建一个新的文件,例如运行touch example或者copy Watcher.class example命令。你会看到下面的变更通知消息:
Someone just create the fiel ‘example1′.
这个简单的示例展示了怎么开始使用Java NIO的功能。同时,它也介绍了NIO.2的Watcher类,它相比较原始的I/O中的轮询方案而言,显得更加直接和易用。
注意拼写错误
当你从这篇文章里拷贝代码时,注意拼写错误。例如,列表1种的StandardWatchEventKinds 对象是复数的形式。即使在Java.net的文档里都把它给拼写错了。
小技巧
NIO里的通知机制比老的轮询方式使用起来更加简单,这样会诱导你忽略对具体需求的详细分析。当你在你第一次使用一个监听器的时候,你需要仔细考虑你所使用的这些概念的语义。例如,知道一个变更什么时候会结束比知道它什么时候开始更加重要。这种分析需要非常仔细,尤其是像移动FTP文件夹这种常见的场景。NIO是一个功能非常强大的包,但同时它还会有一些微妙的“陷阱”,这会给那些不熟悉它的人带来困扰。
同步:需要不断的轮询或者一次得到数据。
异步:不需要不断的轮询或者一次得到数据。
阻塞:调用的时候线程会阻塞。
非阻塞:调用的时候线程不会阻塞。
http://www.cnblogs.com/fanzhidongyzby/p/4098546.html
http://www.cnblogs.com/xuekyo/archive/2013/01/20/2868547.html
netty使用了几个selector,其中一个selector处于客户端的连接,多个seelctor用于处理读和写。其中每一个selector都是一个线程在跑。
netty并没有使用异步非阻塞的IO,使用的是selector,也就是异步的阻塞IO。
http://ifeve.com/netty-reactor-4/
下面的文章讲解了IO模型的使用
https://www.ibm.com/developerworks/cn/linux/l-async/
http://www.cnblogs.com/good-temper/p/5003892.html
http://tutorials.jenkov.com/java-nio/nio-vs-io.html
http://ifeve.com/java-nio-vs-io/
http://www.infoq.com/cn/articles/netty-high-performance
Channel
1、FileChannel FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。
我们无法直接打开一个FileChannel,需要通过使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例。
如:
RandomAccessFile aFile = new RandomAccessFile( "data/nio-data.txt" , "rw" ); |
2 |
FileChannel inChannel = aFile.getChannel(); |
2、因为与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。
3、
Channel的实现
这些是Java NIO中最重要的通道的实现:
-
- FileChannel (transtofm可以在通道之间进行数据传输)
- DatagramChannel(UDP)
- SocketChannel(TCP)
ServerSocketChannel(监听TCP)
4、Pipe pipe可以在多线程之间交换数据
5、URL可以打开一个connection。
http://ifeve.com/java-netword-url-urlconnection/