更多的讲线程的控制,线程的操作,相关的内容,但是考虑到NIO,以NIO为主呢,虽然没有多线程的控制及协作,
提出一些特别的观点,但是他改变了线程应用层的一种协作方式,可以说他解决了一些实际的困难,节省了系统的
成本,AIO叫做异步IO,所谓异步的操作,之前也说过,异步调用会在后台,后台使用线程的方式,去做某件事情,使得前面的
调用就会很快的返回,因此或多或少会和线程又关系,所以在这个地方呢,把NIO和AIO做一个简单的介绍,我们主要是分这几个
部分,我们不会吧NIO做一个很完整的介绍,我们只是把最基本的部分,以及跟线程使用相关的部分,给大家做一个梳理,首先我们会
介绍一个buffer,buffer是NIO基本的一个组件,然后channel,selector,最后看一下AIO相关的一些内容
下面我们来看一下什么是NIO,NIO是new IO的一个简称,与旧式的流相对的,它是一套新的IO的标准,它是在JDK1.4当中
被纳入到JDK当中去的,他主要有这么几个特点,传统的标准的IO呢,基于流的,NIO是基于块的,我们硬盘上存储数据的时候呢,
就是基于块去做存储的,并不是一个字节一个字节存储的,他读写最小单位是块,因此NIO的性能比基于流的要好一些,另外他有
buffer的支持,使用Buffer的对象,对于每一种原始的数据类型呢,都有它对应的一种buffer,他还使用了通道,与标准的IO相比呢,
使用的是流,inputstream,outputstream,这里使用了Channel,作为一个新的原始IO的抽象,他还支持文件锁和内存映射文件,
什么叫文件锁呢,有时候我们会在程序里面看到,程序在运行的时候呢,会生成什么.lock,这个就是文件的锁,当有这个文件存在
的时候呢,表示在当前程序当中呢,有线程来调用了这把锁,其他线程同样要使用这把锁呢,就会陷入等待,它使用完这把锁之后呢,
把这个文件给删除掉,其他线程才能进去做一些相应的操作,那文件系统来实现这个锁,与我们之前讲过的ReentrantLock相比呢,
我们使用内存当中的一个原子整数,AtomicInteger来实现这个锁,这里使用一个文件来实现这个锁,NIO就支持你使用这个方式来写
代码,什么叫内存文件映射,一个文件是在硬盘磁盘里面的,他可以 把整个文件映射到内存里面去,把文件读到内存里面来,这种
方式呢是很快的,比传统的把数据一个个读上来,要快很多,还有对于网络操作来讲呢,提供selector
下面我们来看buffer和channel,buffer缓冲区,它是NIO核心的部分,当你从通道里读数据的时候呢,那你要先写入这个
buffer,当你要从通道里写数据的时候呢,你要先写入buffer,通道就是一个IO的抽象,通道的另一端就是我们要操作的
文件,或者是socket,这里就是一个抽象的通道,我们通过buffer的读写,来对实际的IO进行修改
这里是JAVA当中buffer的实现,基本的类型,基本的类型都有对应的buffer,使用最多的就是byte,
正常我们都是喜欢一个字节一个字节的读数据
下面来看一下简单buffer的使用,这里就是新建了一个文件,新建文件之后拿到的就是InputStream,然后通过这个
InputStream,我们就可以得到Channel,对应的就是这个文件,所以当你往FileChannel里面读数据和写数据的时候呢,
其实你就是对这个文件进行操作,然后我们对ByteBuffer分配了1K的buffer,这个buffer大小是1K,1024个字节,然后
我们就从Channel里面读数据,字节,读到这个buffer里面去,这样buffer里面就存了文件的内容,然后我们可以把这个
Channel给关闭掉,我们会读取buffer里面的数据,flip操作后面会介绍,这里就是我们一般意义上通过buffer进行NIO
的基本操作,大概就是这几个步骤,要搞定一个buffer,然后把channel和buffer的关系处理起来,是read还是write,
最后操作完毕之后呢,我们要做close
复制文件需要一个源和地址,写有fos,表示fileoutputstream,不管源也好目标也好,我们都能够拿到一个channel,
一个通道,源就是read,目标就是write,然后我们分配1K的buffer,我们读和写都要通过buffer去做,首先我们要把
buffer清空,里面什么都没有,然后我们去读这么一段buffer上来,我会尝试去读1K,但是如果有时候你文件里1K都没有,
那显然就读不到1K,这个时候我就需要知道我到底读了多少数据上来,我实际读取的数据,如果我实际读取的数据是-1呢,
我读取完了,文件读到头了,我就break,while就出来了,如果文件还没有读取完呢,我下一步还得继续读,因此在read之后
呢,这个buffer里面的数据是什么呢,实际上就是文件的内容,read文件的内容,然后我们做读写转换,flip我们=在后面会做一个
详细的介绍,这里只需要知道这里是一个读写转换,然后把buffer写到writeChannel当中去,通过这样的一个循环,最后会1K,1K的
把源数据的内容,读出来,然后写进去,读1K写1K,知道最后可能不到1K,那么我们就把最后的部分写进去,最终会读完,-1表示读完了,
读完之后我们就关掉,这个就实现了文件的复制
在buffer当中有三个重要的标记位,其实buffer当中还有很多函数的操作,函数的操作很大一部分呢,修改了
几个位置,我们只要函数的实际含义,位置的实际含义搞清楚以后,我们对他大部分的API,形成了比较容易的理解,
对position,capacity容量做一些调整,位置是什么呢,表示操作当前的缓冲区,当前的buffer,我已经读到,或者写到,
哪个位置了,我下一次读和写呢,我就会从position的下一个位置呢,开始写数据或者读数据,如果是写呢,就从下一个
位置写,读就从下一个位置去读,我当前操作的位置,我知道当前操作的位置呢,我就知道下一个要操作的是什么位置,
容量就是我整个缓冲区的上限,我到底一共能放多少数据,这个Limit上限是什么呢,是我缓冲区的实际上限,这两个是
什么意思呢,比如我整个缓冲区呢,一共有10个byte,那指的是什么呢,我容量有10个byte,我最多不能超过10个byte,但是
如果你去读了一个文件,这个文件可能只有5个byte,这个时候你缓冲区当中,直接用的只有5个byte,因为你文件只有5个byte,
这个时候我认为,简单理解说,上限只有5,虽然你缓冲区一共能放10,但是你后面5个没有放东西,相当于说是没有用的,你只
放了5,我有意义的数据只到5为止,上限之下的数据才有意义的,一定是小于或等于容量的,因为你不可能超过容量,如果你新建
了一个buffer呢,通常情况下和容量是相等的,上限刚才说了,通常会表示有意义的数据的这个位置,因此它代表可读的总容量,
通常和上一次写是相同的,例如你上一些写到5,读我只能读到5,这就是容量和上限的区别,整个缓冲区三个重要的位置呢,
三个属性呢,就是这个位置,容量和上限,尤其是这个容量和上限呢
下面来看一个例子来理解一下,position,limit,这里我们做几个操作,首先我们分配15个字节的缓冲区,
然后我往里面写10个字节,每次都写一个byte进去,写10次,就写10个byte,然后我做一个flip,然后我再读5个
byte出来,然后我再去做一个flip
首先你新建的时候呢,你新建的是15,所以我们当前位置是在哪里呢,位置在这个0,你开始做操作呢,
那我这个limit,capacity就是15,最后一个位置,因为我刚刚成立的时候呢,我容量肯定是最大的容量,那么我的
限制在哪里呢,我的上限肯定也是我的最大上限,还没有做任何操作,我存了10个数据进去,我一个一个往下存,最后的
位置呢,一定是10,我当前的position是在这里,但是我的capacity和limit还是没有动的,因为我只是存数据,我这两个值
是不会动,我put完10个数据以后呢,变成这样子
接着就是一个重点,flip操作,flip操作会做些什么事情呢,他其实做两件事情,第一个我position是0,
把position放到第0个位置上来了,表示我下一个操作是什么位置呢,是从头开始操作的,我刚刚跑到了这里,
但到这里不行,我把你拉回来,接着还有一个非常重要的操作,把limit放到了这个地方,capacity是不会动的,
他表示的是整个缓冲区的容量,你申请的是多少,他还是多少,如果是15他就是15,不会因为你操作的变化而发生
变化,所以一般是不会动的,所以当你做slip之后呢,它是两个操作,第一个是position是0,第二个是limit到了刚才
position的位置,这个位置大家注意,是刚才做操作的时候呢,这个position的位置,然后这个position变成了limit,
这样做有什么意义呢,他的意思在于说,我下一步操作,我自然是从0开始操作,我操作到什么时候为止呢,操作到哪个
位置是有意义的呢,操作到limit是有意义的,换句话说,从position到limit的数据,是我上一次写入的数据,是有意义
的数据,而10到15并不是我上次写入的数据,他很有可能是没有意义的数据,因此如果我这个时候要去读缓存区,那我
只需要读position到limit的数据就可以了,因此flip通常是用在读写转换,在我flip之前,通常是写,我把数据往缓冲区
里写,我flip之后,通常需要去读这个缓冲区,为什么我可以读呢,是因为我位置已经到0了,并且我往下读呢,能够保证我不会
读到没有意义的数据,只会读到limit,他不仅重置了position,还将limit重置到当前的position位置,读写转换的时候呢,
需要执行这个方法
然后再进行5次的读操作,所以position来到这里,limit到这里,然后再做flip,又变成这样子了,
position又归零,limit又跑到position的位置上了,显然这个操作意义并不大,后面的数据相当于没有用了
Buffer还支持下面几个比较重要的操作,flip我们已经讲过了,在读写转换的时候,是非常有用的,
此外还有rewind,将position清零,这里只是简单的把position给拉回来,有时候我们可能需要一遍一遍的去扫描
缓冲区,扫一遍还拿不到足够的信息来做判断,因为有些时候数据结构可能会比较复杂一些,所以我们可能需要扫两遍,
这个时候我们就可以把position拿到前面去扫描第二遍,你把position清空,limit是不会变的,不像flip一样,把limit
也变一下,这样你第二次读的数据和第一次读的数据未必是一样的,clear清空,清空什么意思呢,就是我position置0了,
我limit设置为capacity大小,清空之后我们可以认为里面的数据都没有意义的,因为你不知道有意义的数据放在哪个位置,
比如我本来是0到15,我0到10是有意义的,然后我把limit也设置为15,这个时候你就不知道读到什么时候为止,这个数据才是
有效的,我清空之后一般不会去读,clear之后一般会做写入的操作,这就是buffer常用的几个函数
下面就是关于文件映射到内存,这个很简单,把fc这个channel,直接映射到内存当中,这样整个文件给读进来了,
然后你对MappedByteBuffer的修改呢,修改这个文件,这个速度是非常快的,要比你传统的修改要快
上面这些基础之后呢,我们看看NIO的网络编程,首先我们介绍一般意义上的多线程的结构,我们这个多线程的服务器呢,
一个简单的做法呢,一般是这样做的,有多个客户端,去连接服务器,有一个派发线程,线程只做accept,accept客户端之后呢,
不会做实际的业务处理,它会把接到的客户端呢,派发给某一个线程做处理,当然把socket给线程,每个线程对应一个客户端,
做这样的一个处理,这个时候你有100个客户端过来,排100个线程,去做这个处理,那么在这个处理当中呢,我们当然也会涉及
到数据的读和写,像这样的一个结构呢
我们会实现一个简单的多线程的服务器,读到了客户端的数据呢,就往服务端回写相同的数据,
他读到hello,他就回写hello,他读到world,他就回写world,那么这个程序就是说,相对来说还是比较简单的,
把这个socket建立起来,做一个监听,8000端口做一个监听,等待accept,等待客户端连接,如果等到之后呢,
通过线程池去执行这个操作,把这个消息进行处理,处理这个消息,处理这个消息的时候呢,它使用了一个新的线程,
线程池里挑一个线程做处理,同时很重要的一点呢,clientSocket,是哪一个客户端要把它带过去,新的线程和客户端
进行一个通信,主线程只做accept,做客户端的接入
先从客户端里读进来,把客户端发的数据读进来,读到的是什么,就发出去,outputstream就是从socket里面读出来的,
那么在这个时候呢,我们就会把读到的数据写回去,这里我们还统计了一下时间,我们读之间记录一个时间戳,写完之后
写一个时间戳,我们这个线程实际上,从读到写,一共花了多长时间,最后我们会关闭一些资源
看一下客户端,客户端就新建一个socket,然后去连8000端口,它会有一个PrintWriter,然后去print这个信息,
然后呢再去读,读到的数据应该是服务器返回的数据,读的的时候也是从socket里面,inputstream里面去读这个数据,
把这个信息呢做一个输出,执行代码我们就看到客户端,就会读到客户端的输出
传统的网络编程,使用这种方式做的时候呢,它会产生什么问题呢,我们可以来解释一下,在这种模式之下呢,
其实系统是为每一个客户端,去使用的一个线程,每个客户端使用一个线程,系统当中最不可靠的就是网络,
CPU也好,内存也好,硬盘也好,都还是比较可靠地,如果你从硬盘里面读不到数据,从内存里面读不到数据,那基本上
就是硬件上的问题,但是网络问题呢,比较特殊,网络有各种问题你可能读不到,网络拥塞,网络非常的延迟,可能会出现
大量的丢包,有可能网线没有插好,WIFI出现了什么问题,都是有可能的,信号太弱,因此网络相对来讲呢,不是一个很可靠的
一个东西,因此有可能在网络传输的时候呢,可能出现延迟中断等等,对整个系统交互产生影响,线程是一对一的服务的,我
新建一个线程就是为客户端服务的,客户端出现问题的时候呢,或者网络出现问题的时候呢,线程可能会占用比较长的时间,
因为数据的准备和读取呢,都是由新建的线程负责,如果说客户端数量众多,大量的并发连接,这些并发连接,有若干个不太好的
状态呢,导致线程会卡死在那边,就会花很多时间在客户端,可能会发生比较大的消耗,NIO就是用来解决这个问题,它是个非
阻塞的IO,非阻塞什么意思呢,我读取数据,不需要去做等待,为什么说我读取数据不需要等待呢,我来一个线程一个客户端,
线程在读客户端数据的时候呢,当然就得等待了,我要等你这个数据读完,我才能把这个数据拿下来,但是IO操作通常会分为
两个部分,一个是准备,准备IO,一个是真正的去做IO,非阻塞是指说,我准备去做IO的时候,我不会在这里做线程的阻塞,
我只有当这个数据准备好了之后呢,我去读一下,去写一下,因此NIO可以认为是一个非阻塞的IO,如果客户端反应是很迟钝的,
很缓慢的,或者出现延迟等东西,这延时的时间呢,其实不会出现在NIO的线程当中,因为如果你没有准备好数据,那么其实这个
线程是不会开始工作的
这个客户端做connect之后,他去做输出,每做一个输出就要休眠,休眠一秒钟,他休眠了6秒钟时间,这个模拟了一个
客户端延迟比较严重,客户端他可能会出现各种各样的问题,同样客户端去连这个服务器,因此在传统的IO上面呢,
我们会得到这么一个输出,因为客户端发任何的写数据,或者网络延迟比较严重,他每处理这个数据都要花6秒钟的
时间,这里开了若干个客户端线程在这里做
刚刚看到的6秒钟时间就是读和写的时间,这里可以看到的呢,6秒钟时间花在哪里呢,因为你读的时候读不到数据,
数据根本就没有准备好,写的很慢,这回导致服务器的输出是这样子,每个线程都被卡了6秒钟
如果NIO来处理这个问题会怎么做呢,首先Channel有点类似Socket的流,一个Channel可以对应的是文件或者网络Socket流,
这个当然我们在网络编程当中呢,对应的是Socket,这个Channel有一种selectable channel,可选的Channel,可选的Channel
是什么意思呢,他可以被轮询,可以被选择,那么selector是什么东西呢,它是个选择器,他可以选中某一个Channel,做这个数据
的处理,一个selector他只对应一个线程,一个线程只对应一个selector,一个selector就可以轮询多个channel,这是很关键的,
一个selector可以轮询多个channel,一个channel对应的是一个socket,换句话说,就是一个线程轮询了多个socket,selector
通常情况之下,他要看channel,客户端,每个socket就是客户端,他看哪个客户端准备好了,有两种方式,一个叫select,一个叫
selectnow,我们现在讲select,我现在已经有客户端准备好数据了,如果说没有客户端准备好数据,那你在selector上面调用
select方法呢,是会阻塞的,虽然NIO是非阻塞的,非阻塞也不是一个绝对上的含义,当你没有数据准备好的时候,实际上是无事可做的,
当你有数据准备好的时候,select就返回,返回之后呢,你实际上在后面可以得到一个selectKey的东西,那你只有在执行select
之后才可以执行selectKey,selectionKey其实表示一对selector和channel,关系就是selector和channel,我在每一个selector
上面,发现哪个channel是数据已经准备好的,只有准备好的数据的channel,client socket,才会被select选中,才会触发select
做返回,如果没有一个数据准备好的,这select是被阻塞,不会返回的,这样就使得我一个线程,我可以同时监控多个客户端,我不管
有几个,而当你客户端反应很迟钝的时候,我只是说这个线程本身不会做返回,做这个阻塞,但是我只有一个线程在里边,或者说
我这个线程数量是很少的,因此我极少的线程监控大量的客户端,对我整个系统的影响相对来说要好一些,如果我有大量的客户端,
每个客户端都阻塞一个线程,那么这个时候我又大量的线程会阻塞,而我有极少量的线程,来监控我大量的客户端,计算大家都阻在
那里不动了,我也只有少量的线程受到影响,如果我真的监控大量的客户端,不可能所有的客户端都出问题,都在里面不动,总有
几个是准备好的,给我返回的,你准备好了过来就行了,那我就可以拿到selectionKey,那我从selectionKey当中,我就可以拿到
selectionKey和channel,因为channel里面是有数据的,因为channel后面代表socket,你既然数据准备好了,那我就把这个channel
拿过来,把数据读出来就可以了,那么在这个读数据的过程当中,你就不需要做一些等待操作,因为我是准备好了才被选上来的,
那selectNow是什么意思呢,selectNow是和select功能是一样的,他也是选择已经准备好的socket,他跟select的区别就是,select
会阻塞,如果当前没有数据准备好,他就卡在那边不动了,直到有人准备好之后才开始,selectNow他不阻塞,它是直接返回,如果当前
没有人准备好,就返回0,如果有人准备好,就返回多少个客户端准备好了,所以这个是不阻塞的,如果你要完全的不阻塞的话,你可以选择
selectNow,但是大部分的情况下呢,这个select已经够用了,因为当你没有数据准备好的时候,你确实是应该陷入一个等待
下面我们来参考一下代码,因为代码会比较长,感觉不是很好读
https://my.oschina.net/hosee/blog/615269
package com.learn.thread;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MultiThreadNIOEchoServer {
public static Map<Socket, Long> geym_time_stat = new HashMap<Socket, Long>();
class EchoClient {
private LinkedList<ByteBuffer> outq;
EchoClient() {
outq = new LinkedList<ByteBuffer>();
}
public LinkedList<ByteBuffer> getOutputQueue() {
return outq;
}
public void enqueue(ByteBuffer bb) {
outq.addFirst(bb);
}
}
class HandleMsg implements Runnable {
SelectionKey sk;
ByteBuffer bb;
public HandleMsg(SelectionKey sk, ByteBuffer bb) {
super();
this.sk = sk;
this.bb = bb;
}
@Override
public void run() {
// TODO Auto-generated method stub
EchoClient echoClient = (EchoClient) sk.attachment();
echoClient.enqueue(bb);
sk.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
selector.wakeup();
}
}
private Selector selector;
private ExecutorService tp = Executors.newCachedThreadPool();
private void startServer() throws Exception {
selector = SelectorProvider.provider().openSelector();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
InetSocketAddress isa = new InetSocketAddress(8000);
ssc.socket().bind(isa);
// 注册感兴趣的事件,此处对accpet事件感兴趣
SelectionKey acceptKey = ssc.register(selector, SelectionKey.OP_ACCEPT);
for (;;) {
selector.select();
Set readyKeys = selector.selectedKeys();
Iterator i = readyKeys.iterator();
long e = 0;
while (i.hasNext()) {
SelectionKey sk = (SelectionKey) i.next();
i.remove();
if (sk.isAcceptable()) {
doAccept(sk);
} else if (sk.isValid() && sk.isReadable()) {
if (!geym_time_stat.containsKey(((SocketChannel) sk
.channel()).socket())) {
geym_time_stat.put(
((SocketChannel) sk.channel()).socket(),
System.currentTimeMillis());
}
doRead(sk);
} else if (sk.isValid() && sk.isWritable()) {
doWrite(sk);
e = System.currentTimeMillis();
long b = geym_time_stat.remove(((SocketChannel) sk
.channel()).socket());
System.out.println("spend:" + (e - b) + "ms");
}
}
}
}
private void doWrite(SelectionKey sk) {
// TODO Auto-generated method stub
SocketChannel channel = (SocketChannel) sk.channel();
EchoClient echoClient = (EchoClient) sk.attachment();
LinkedList<ByteBuffer> outq = echoClient.getOutputQueue();
ByteBuffer bb = outq.getLast();
try {
int len = channel.write(bb);
if (len == -1) {
disconnect(sk);
return;
}
if (bb.remaining() == 0) {
outq.removeLast();
}
} catch (Exception e) {
// TODO: handle exception
disconnect(sk);
}
if (outq.size() == 0) {
sk.interestOps(SelectionKey.OP_READ);
}
}
private void doRead(SelectionKey sk) {
// TODO Auto-generated method stub
SocketChannel channel = (SocketChannel) sk.channel();
ByteBuffer bb = ByteBuffer.allocate(8192);
int len;
try {
len = channel.read(bb);
if (len < 0) {
disconnect(sk);
return;
}
} catch (Exception e) {
// TODO: handle exception
disconnect(sk);
return;
}
bb.flip();
tp.execute(new HandleMsg(sk, bb));
}
private void disconnect(SelectionKey sk) {
// TODO Auto-generated method stub
//省略略干关闭操作
}
private void doAccept(SelectionKey sk) {
// TODO Auto-generated method stub
ServerSocketChannel server = (ServerSocketChannel) sk.channel();
SocketChannel clientChannel;
try {
clientChannel = server.accept();
clientChannel.configureBlocking(false);
SelectionKey clientKey = clientChannel.register(selector,
SelectionKey.OP_READ);
EchoClient echoClinet = new EchoClient();
clientKey.attach(echoClinet);
InetAddress clientAddress = clientChannel.socket().getInetAddress();
System.out.println("Accepted connection from "
+ clientAddress.getHostAddress());
} catch (Exception e) {
// TODO: handle exception
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
MultiThreadNIOEchoServer echoServer = new MultiThreadNIOEchoServer();
try {
echoServer.startServer();
} catch (Exception e) {
// TODO: handle exception
}
}
}
private static ExecutorService tp= Executors.newCachedThreadPool();
private static final int sleep_time=1000*1000*1000;
public static class EchoClient implements Runnable{
public void run(){
try {
client = new Socket();
client.connect(new InetSocketAddress("localhost", 8000));
writer = new PrintWriter(client.getOutputStream(), true);
writer.print("H");
LockSupport.parkNanos(sleep_time);
writer.print("e");
LockSupport.parkNanos(sleep_time);
writer.print("l");
LockSupport.parkNanos(sleep_time);
writer.print("l");
LockSupport.parkNanos(sleep_time);
writer.print("o");
LockSupport.parkNanos(sleep_time);
writer.print("!");
LockSupport.parkNanos(sleep_time);
writer.println();
writer.flush();
}catch(Exception e)
{
}
}
}
NIO会将数据准备好之后,再交由应用进行处理,因此数据没有准备好呢,是跑不到你应用里面去的,
从这个角度来说呢,他可以节省系统资源,读取和写入过程本身呢,是在应用线程本身完成的,就是当你真正的
去读,只是把等待的时间剥离到单独的线程去等待,从而提高运行的效率
来看一下AIO异步IO,对于NIO来讲,数据准备好了再来通知我,但是读数据和写数据的事情,还是要我这个线程来做的,
他只是把我等待IO的时间呢,全部剥离到极少线程中去完成,避免大量的IO线程进行等待,造成资源的浪费,但是AIO更进一步,
等你读完了或者写完了再通知我,对于我们应用来说,你既不需要读也不需要写,你只需要等到那里就行了,你只需要系统把数据
都处理完了之后呢,你把你的回调函数放进去,就会来执行,这里要注意的是呢,AIO并不会加快IO,IO变多快,其实不管什么方式,
包括前面的NIO也好,IO本身的速度并没有加快的,它是改变了原来对IO的处理方式,而导致看起来你系统的性能变更高,我们只是
进行了一个更加合理的线程上的调度,因此AIO并不会把IO本身变快,他只说说你读完或者写完之后呢,给我一个通知,我什么时候
拿到这个通知,我什么时候做这个事情,当我没有写完的时候呢,我也是不干活的,但是我这个不干活的时候呢,我可以去处理其他的
一些事情,从而把这个时间充分的利用起,AIO当中使用的时候呢,定义大量的回调函数,业务处理
AIO就是用这个类,AsynchronousServerSocketChannel,异步的ServerSocketChannel,使用这种方式可以打开Socket,
在这个端口上进行监听,如果你要去accept的时候呢,它是异步IO,accept本身不可以去阻塞,因为阻塞你就谈不上异步了,
异步是马上返回的,但是我马上返回我怎么做事情呢,照理说你是有一个accept上来了,然后我拿到accept的信息,然后我才能够
干活,他们这里是有一个回调,回调就是CompletionHandler,在你accept的时候呢,你接受一个回调函数,实际上是一个接口,回调接口
做什么呢,做处理,处理什么呢,处理当有人真正accept上来的时候呢,我会来调用你这个handler,然后我把我这个accept相关的
信息呢,传给你,然后你再去做accept之后的一些逻辑,这个就是AIO的基本思想
那我们来看一下异步的ServerSocketChannel的方法,最重要的是read和write,read就是从socket里面读数据,
因为我是异步方法,所以我读数据的时候呢,那我自然是不能够等待我读完了再返回,那就不叫异步了,所有的read
方法都是立即返回的,我们先来看第一个方法,第一个方法我读到哪里去,byteBuffer,读到哪里去,读到这个地方去,
这里的long和TimeUnit,是指读数据的超时时间,如果超时我就不读了,我读完之后做什么呢,就做这个事情,就把完成的
Handler,调用一下,第二个函数也是类似的,我读到这个地方去,读到这个buffer里面去,然后我读完之后呢,他的返回值
是什么,返回值是Future,一个Future模式,它能够马上返回,但是没有读完,没有读完会给你一个Future,Future里面有一个
Integer,告诉你将来读完之后呢,告诉你到底读到了多少个字节,最后一个也是类似的,但是最后一个会比较复杂,它会把一个
Socket数据,读到byteBuffer数组当中去,当然数组里面可以指定一个offset,你准备读到哪些buffer里面,你不一定从第0个
开始,可以从第一个或者第二个写数据,这个通常是用来做什么的呢,有时候我们一些网络协议报文,头部通常情况下,可能
是固定的,因为头不可变的话,处理起来会非常的麻烦,因此有些网络协议报文,这个时候我们可以是使用这种方式呢,很快的
把头部给剥离出来,比如你头部是20个字节,那我就定义一个byteBuffer的数组,其中第一个就是24,很快就把这个24个字节给
剥离掉,读完之后也是到CompletionHandler里面去,写是非常类似的,write的时候也是返回一个Future,写的时候会立即返回,
将来某个时间段呢,把这个Future拿到呢,Future到底写完了没有,到底写了多少字节,有关这个东西呢
用异步IO来做异步的实现,大家注意这个代码虽然看起来很长,但是他只有一个函数,就是accept,后面接收了一个
参数,这个参数就是completionHandler,这个CompletionHandler的定义呢,他就特别长,这里只是执行了一个accept
而已,当然这里看起来是很长的代码,但是在执行的时候呢,其实是立即返回的,立马就会执行完,只是把completionHanlder
传进去而已,当他accept完了之后,在这里我们使用result呢,我们使用一个future的函数,我们限时100秒,如果超过100秒
还没有读到数据呢,读完之后我们就进行读写转换,然后去写,这个写还是返回的Future,这里就是AIO的一个使用