同步(synchronous)/异步(asynchronous),阻塞(blocking)/非阻塞(non-blocking)两组概念在不同场合有不同的含义。
在操作系统中
阻塞状态是指正在执行的进程由于发生某事情而暂时无法继续执行时,便放弃处理机而处于暂停状态。
进程具有异步性,指:进程各自独立,不可预知的速度向前推进,或者说进程实体按异步的方式运行。正式由于有异步性,所以要同步。
进程同步:对多个相关进程在执行次序上进行协调,以使并发执行的进程之间能有效共享资源和互相合作,从而使程序的执行具有可再现性。
这里讲的同步异步需在在多个进程之间,是讲进程推进的方式。异步就是进程之间没有关系,互不干扰。同步就是进程有关系,有干扰关系的。
在通信技术上
这里的同步异步,是讲调用方是否感知到被调用方调用成功了并调用方能否得到反馈。
比如,小明他妈让小明去车站接人,小明到了车站一看客人还没到,不返回家里告诉他妈,而是一直等到客人到达之后才返回告诉他妈。这就是同步的通信。
比如,还是小明他妈让小明去车站接人,但是客人到达的时间并不确定,然后每隔十分钟小明都会再去车站看一遍客人到没到,立即返回家里告诉他妈。这就是异步的通信。
在IO操作上
阻塞和非阻塞是指当进程访问的数据如果尚未就绪,进程是否需要等待。
比如,小明他妈让小明去车站接人,在小明把客人接到家之前,小明他妈什么都不干,在家里傻等。这就是阻塞的IO模型。
比如,还是小明他妈让小明去车站接人,在小明把客人接到家之前,小明他妈可以在家里洗菜、做饭、打扫卫生,直到客人到达,再停下手中的或去迎接客人。这就是非阻塞的IO模型。
同步和异步是指访问数据的机制,同步一般指主动请求并等待I/O操作完毕的方式,当数据就绪后在读写的时候必须阻塞(区别就绪与读写两个阶段,同步的读写必须阻塞)。异步则指主动请求数据后便可以继续处理其它任务,随后等待I/O操作完毕的通知,这可以使进程在数据读写时也不阻塞,但是会等待“通知”。
同步大多是阻塞的,但是也有非阻塞。因为线程可以干别的事情,不一定一直等待,可以轮询。
异步大多为非阻塞的,但是也可以阻塞。因为线程可以不干别的事情,一直在等待通知。
同步与异步、阻塞与非阻塞之间有很多相似的地方,在一些资料中两者等同,是可以互换的。
java中的NIO
java 6版本开始引入的NIO(即 new IO)包,通过Selecters提供了非阻塞式的IO。
Java NIO的通道类似流,但又有些不同:
(1)既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
(2)通道可以异步地读写。
(3)通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。
面向流与面向缓冲
Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 JavaIO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。
Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
阻塞与非阻塞IO
Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。
Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
选择器(Selectors)
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
Selector允许单线程处理多个Channel。如果应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。
下面是在一个单线程中使用一个Selector处理3个Channel的图示:
要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。
在通道上可以注册我们感兴趣的事件。一共有以下四种事件:
服务端接收客户端连接事件 SelectionKey.OP_ACCEPT
客户端连接服务端事件 SelectionKey.OP_CONNECT
读事件 SelectionKey.OP_READ
写事件 SelectionKey.OP_WRITE
Java NIO相对于旧的java.io库来说,并不是要取代,而是提出的三个新的设计思路:
(1)对原始类型的读/写缓冲的封装
(2)基于Channel的读写机制,对Stream的进一步抽象。
(3)事件轮询/反应设计模式(即Selector机制)
按上述思路,而Channel机制是作为Stream的进一步抽象而产生的,那么Channel和Stream相比有什么不同呢?按字面理解实际上就可以获得信息:Stream作为流是有方向的,而Channel则只是通道,并没有指明方向。因此,读写操作都可以在同一个Channel里实现。Channel的命名强调了nio中数据输入输出对象的通用性,为非阻塞的实现提供基础。
在Channel的实现里,也存在只读通道和只写通道,这两种通道实际上抽象了Channel的读写行为。
至于Channel的IO阻塞状态读写,则和传统的java.io包类似。但多了一层缓冲而已。因此,按照原来的设计思路来用nio也是可行的,不过nio的设计本质上我觉得还是非阻塞输入输出控制,把控制权重新交给程序员。
因此,java.nio从设计角度看,就不是替代java.io包,而是为java.io提供更多的控制选择。
Channel和Buffer的关系
Buffer的作用主要体现在Channel的非阻塞状态下。Channel如果不是阻塞的,那么每次调用必须立即返回,那样,读写操作都不至于影响对方。那么,立即返回意味着能够读写多少数据呢?这是不确定的,依赖于当前的传输状况。因此,作为缓冲的Buffer就为信息的完整性提供一个保障。每次读写操作都先把数据放到Buffer里面,然后多次调用Channel的读写方法对数据进行操作,依靠对Buffer的状态来判断数据的完整性。
以下是Java NIO里关键的Buffer实现:
ByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
这些Buffer覆盖了能通过IO发送的基本数据类型:byte,short, int, long, float, double 和 char。
Java NIO 还有个Mappedyteuffer,用于表示内存映射文件。
正如上面所说,从通道读取数据到缓冲区,从缓冲区写入数据到通道。
上面的图描述了从一个客户端向服务端发送数据,然后服务端接收数据的过程。客户端发送数据时,必须先将数据存入Buffer中,然后将Buffer中的内容写入通道。服务端这边接收数据必须通过Channel将数据读入到Buffer中,然后再从Buffer中取出数据来处理。
非阻塞式IO实例
java NIO中的重要实现类:
FileChannel 从文件中读写数据。
DatagramChannel 能通过UDP读写网络中的数据。
SocketChannel 能通过TCP读写网络中的数据。
ServerSocketChannel 可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。
下面利用SocketChannel与ServerSocketChannel来实现非阻塞式的网络通信。
服务端:
import java.io.IOException; import java.net.InetSocketAddress; 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.util.Iterator; public class NIOServer { //通道管理器 private Selector selector; /** * 获得一个ServerSocket通道,并对该通道做一些初始化的工作 *@param port 绑定的端口号 *@throws IOException */ public void initServer(int port) throws IOException { // 获得一个ServerSocket通道 ServerSocketChannel serverChannel =ServerSocketChannel.open(); // 设置通道为非阻塞 serverChannel.configureBlocking(false); // 将该通道对应的ServerSocket绑定到port端口 serverChannel.socket().bind(newInetSocketAddress(port)); // 获得一个通道管理器 this.selector = Selector.open(); //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后, //当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。 serverChannel.register(selector, SelectionKey.OP_ACCEPT); } /** * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 */ public void listen() throws IOException { System.out.println("服务端启动成功!"); // 轮询访问selector while (true) { //当注册的事件到达时,方法返回;否则,该方法会一直阻塞 selector.select(); // 获得selector中选中的项的迭代器,选中的项为注册的事件 Iterator ite =this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key =(SelectionKey) ite.next(); // 删除已选的key,以防重复处理 ite.remove(); // 客户端请求连接事件 if(key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key .channel(); // 获得和客户端连接的通道 SocketChannelchannel = server.accept(); // 设置成非阻塞 channel.configureBlocking(false); //在这里可以给客户端发送信息 channel.write(ByteBuffer.wrap(newString("向客户端发送了一条信息").getBytes())); //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。 channel.register(this.selector,SelectionKey.OP_READ); // 获得了可读的事件 } else if(key.isReadable()) { read(key); } } } } /** * 处理读取客户端发来的信息 的事件 *@param key *@throws IOException */ public void read(SelectionKey key) throws IOException{ // 服务器可读取消息:得到事件发生的Socket通道 SocketChannel channel =(SocketChannel) key.channel(); // 创建读取的缓冲区 ByteBuffer buffer = ByteBuffer.allocate(10); channel.read(buffer); byte[] data = buffer.array(); String msg = newString(data).trim(); System.out.println("服务端收到信息:"+msg); ByteBuffer outBuffer =ByteBuffer.wrap(msg.getBytes()); channel.write(outBuffer);// 将消息回送给客户端 } /** * 启动服务端测试 *@throws IOException */ public static void main(String[] args)throws IOException { NIOServer server = new NIOServer(); server.initServer(8000); server.listen(); } }
客户端:
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; public class NIOClient { //通道管理器 private Selector selector; /** * 获得一个Socket通道,并对该通道做一些初始化的工作 *@param ip 连接的服务器的ip *@param port 连接的服务器的端口号 *@throws IOException */ public void initClient(String ip,int port) throws IOException { // 获得一个Socket通道 SocketChannel channel =SocketChannel.open(); // 设置通道为非阻塞 channel.configureBlocking(false); // 获得一个通道管理器 this.selector = Selector.open(); // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调 //用channel.finishConnect();才能完成连接 channel.connect(newInetSocketAddress(ip,port)); //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。 channel.register(selector,SelectionKey.OP_CONNECT); } /** * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 *@throws IOException */ public void listen() throws IOException { // 轮询访问selector while (true) { selector.select(); // 获得selector中选中的项的迭代器 Iterator ite =this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key =(SelectionKey) ite.next(); // 删除已选的key,以防重复处理 ite.remove(); // 连接事件发生 if (key.isConnectable()){ SocketChannelchannel = (SocketChannel) key .channel(); // 如果正在连接,则完成连接 if(channel.isConnectionPending()){ channel.finishConnect(); } // 设置成非阻塞 channel.configureBlocking(false); //在这里可以给服务端发送信息 channel.write(ByteBuffer.wrap(newString("向服务端发送了一条信息").getBytes())); //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。 channel.register(this.selector,SelectionKey.OP_READ); // 获得了可读的事件 } else if(key.isReadable()) { read(key); } } } } /** * 处理读取服务端发来的信息 的事件 *@param key *@throws IOException */ public void read(SelectionKey key) throwsIOException{ //和服务端的read方法一样 } /** * 启动客户端测试 *@throws IOException */ public static void main(String[] args)throws IOException { NIOClient client = new NIOClient(); client.initClient("localhost",8000); client.listen(); } }
原文地址:http://blog.csdn.net/u012152619/article/details/46041771