NIO之前:如果你需要编写一个Java服务器,为了实现异步操作,你必须为每个连接请求生成一个Java线程,当连接请求很多时,线程的调度,上下文切换,所付出的 代价是非常昂贵,而且由于Java是跨平台的,各个平台对线程的支持并不相同,性能也不相同,因此传统的Java服务器编程架构是低效的且代价贵,dl大 侠写了个util.concurrent包后,总算是减轻了线程调度给java程序员带来的痛苦,但是相比之与C、C++写出来的服务器,java服务器 在性能要求很高的情况下,基本上没有什么竞争力,甚至是入围的权利的都没有
NIO是一个基于事件的IO架构,最基本的思想就是:有事件我通知你,你再去做你的事情,没事件时你大可以节约大把时间去做其它任何事情。而且NIO的主线程only one,不像传统的模型,需要N个线程去,也减轻了JVM的工作量,使得JVM处理任务时显得更加高效。
三、 异步Socket中应当注意的事情
1. 读
异步socket最基本的理念就是事件通知,前面也说了,有事件通知你了,你才该做你应当做的事情。在异步socket中当注册了一个OP_READ事件后,你就等着Selector通知你吧,如果没有通知你,你在家睡大觉都行。
在这里,我们有人出现的错误就是受同步的影响,自己去主动读,而且还搞出了多线程,如果仔细考虑一下,就不会出现这个问题了。同步socket中,调用
read方法读取IO中的数据时,通常情况下如果没有数据read方法会阻塞,且是同步的,所以当多个线程同时访问时,read方法是线程安全的。
而在异步下就不同,异步是不会阻塞的,有什么就返回什么,你主动去读,只要有数据,你就可以拿走,在多线程的情况下,也许你是想让第一个线程读 取,but此时来数据时正好是线程2读到了,那线程2就高高兴兴的拿去,而线程1还在苦苦等待,这样导致数据混乱不说,如果后面再也不来数据了,线程1就 是死循环啦。
2. 写
在异步socket中,写是唯一一个主动点的操作,但是也不能直接去写Channel,而是应当先把自身注册为OP_WRITABLE,这时 Selector就会发现你的存在,并把给发一个write事件,你这时后就可以写了,不过这时候有个小小的技巧,就是你执行写操作之前,请取消掉你的写 注册,否则你的cpu肯定是100%。
3. 等待
在传统的服务器编程中,由于对于每个请求都是产生的一个线程,因此你在你每个请求线程中wait也好,sleep也好,不会影响别人。但是异步不 同,他的主线程只有一个,基本上每个处理都是线性的,也就是说处理完第一个,然后才能处理第二个,因此nio是一个极好的处理短连接的架构。
我们现在出现的问题是,有人受同步的影响,没有搞清异步是如何处理,竟然在方法处理中用上sleep,而且一等还是3秒,这意味着什么,3秒才能处理一个请求,My god,我要一个3秒才能处理一个请求的服务器干嘛啊,还是60年代啊:(
如果出现这样的需要等待的情况,应当另起一个线程(推荐使用线程池)去完成这个“长”时间的任务,或者将其它交给一个消息队列,通过发消息的方式将给别人去完成也行,客户端能等,你服务器怎么也能等呢?写出这样的代码,基本上一个服务器也就废了。
java.nio.Buffer的一些基础知识的备忘
capacity(): 表明缓冲区的容量大小, 一旦确定了大小, 将不能再改变;
limit(): 告诉您到目前为止已经往缓冲区填了多少字节,或者用#limit(int)来改变这个限制;
position(): 告诉您当前的位置,以执行下一个读/写操作;
mark(): 为了稍后用 reset() 进行重新设置而记住某个位置;
flip(): 交换限制指针和位置指针,然后将位置置为 0,并废弃已经做的mark标记。
缓冲区的基本操作是读#get()和写#put()。
缓冲区类型:
NIO具有7种特定的 Buffer 类型,每种类型对应着一个基本数据类型(除了 boolean)
- ByteBuffer //字节缓冲区
- CharBuffer //字符缓冲区
- DoubleBuffer //double 缓冲区
- FloatBuffer //float 缓冲区
- IntBuffer //int 缓冲区
- LongBuffer //long 缓冲区
- ShortBuffer //short 缓冲区
缓冲区分为直接缓冲区和间接缓冲区,直接缓冲区的创建成本要高于间接缓冲区,但同时它也会提供更快的I/O访问速度。所以直接缓冲区适合那种长期存在的缓冲区,而间接缓冲区则适合于生命周期较短的。还有,要注意,只有ByteBuffer可以创建直接缓冲区。
缓冲区的几个内部状态的改变:
容量(capacity),缓冲区大小
限制(limit),第一个不应被读取或写入的字节的索引,总是小于容量。
位置(position),下一个被读取或写入的字节的索引,总是小于限制。
0 <= position <= limit <= capacity
1. clear()方法:设置limit为capacity,position为0,并丢弃mark。
2. filp()方法:设置limit为当前position,然后设置position为0,并丢弃mark。
3. rewind()方法:保持limit不变,设置position为0,并丢弃mark。