想达到的目的有两个:
1。深入的理解同步与异步、阻塞与非阻塞,这看似烂大街的词汇很多人已经习惯不停的说,但却说不出其中的所以然,包括我。
2。理解各种IO模型在Java网络IO中的运用,能够根据不同的应用场景选择合适的交互方式。了解不同的交互方式对IO性能的影响。
前提
首先先强调上下文:下面提到了同步与异步、阻塞与非阻塞的概念都是在IO的场合下。它们在其它场合下有着不同的含义,比如操作系统中,通信技术上。
然后借鉴下《Unix网络编程卷》中的理论:
IO操作中涉及的2个主要对象为程序进程、系统内核。以读操作为例,当一个IO读操作发生时,通常经历两个步骤:
1,等待数据准备
2,将数据从系统内核拷贝到操作进程中
例如,在socket上的读操作,步骤1会等到网络数据包到达,到达后会拷贝到系统内核的缓冲区;步骤2会将数据包从内核缓冲区拷贝到程序进程的缓冲区中。
阻塞(blocking)与非阻塞(non-blocking)IO
IO的阻塞、非阻塞主要表现在一个IO操作过程中,如果有些操作很慢,比如读操作时需要准备数据,那么当前IO进程是否等待操作完成,还是得知暂时不能操作后先去做别的事情?一直等待下去,什么事也不做直到完成,这就是阻塞。抽空做些别的事情,这是非阻塞。
非阻塞IO会在发出IO请求后立即得到回应,即使数据包没有准备好,也会返回一个错误标识,使得操作进程不会阻塞在那里。操作进程会通过多次请求的方式直到数据准备好,返回成功的标识。
想象一下下面两种场景:
A 小明和小刚两个人都很耿直内向,一天小明来找小刚借书:“小刚啊,你那本XXX借我看看”。
于是小刚就去找书,小明就等着,找了半天找到了,把书给了小明。
B 小明和小刚两个人都很活泼外向,一天小明来找小刚借书:“嘿小刚,你那本XXX借我看看”。
小刚说:“我得找一会”,小明就去打球去了。过会又来,这次书找到了,把书给了小明。
结论:A是阻塞的,B是非阻塞的。
从CPU角度可以看出非阻塞明显提高了CPU的利用率,进程不会一直在那等待。但是同样也带来了线程切换的增加。增加的 CPU
使用时间能不能补偿系统的切换成本需要好好评估。
同步(synchronous)与异步(asynchronous)IO
先来看看正式点的定义,POSIX标准将IO模型分为了两种:同步IO和异步IO,Richard
Stevens在《Unix网络编程卷》中也总结道:
A synchronous I/O operation causes the requesting process to be blocked until
that I/O operation completes;
An asynchronous I/O operation does not cause
the requesting process to be blocked;
可以看出,判断同步和异步的标准在于:一个IO操作直到完成,是否导致程序进程的阻塞。如果阻塞就是同步的,没有阻塞就是异步的。这里的IO操作指的是真实的IO操作,也就是数据从内核拷贝到系统进程(读)的过程。
继续前面借书的例子,异步借书是这样的:
C 小明很懒,一天小明来找小刚借书:“嘿小刚,你那本XXX借我看看”。
小刚说:“我得找一会”,小明就出去打球了并且让小刚如果找到了就把书拿给他。小刚是个负责任的人,找到了书送到了小明手上。
A和B的借书方式都是同步的,有人要问了B不是非阻塞嘛,怎么还是同步?
前面说了IO操作的2个步骤:准备数据和把数据从内核中拷贝到程序进程。映射到这个例子,书即是准备的数据,小刚是内核,小明是程序进程,小刚把书给小明这是拷贝数据。在B方式中,小刚找书这段时间小明的确是没闲着,该干嘛干嘛,但是小刚找到书把书给小明的这个过程也就是拷贝数据这个步骤,小明还是得乖乖的回来候着小刚把书递手上。所以这里就阻塞了,根据上面的定义,所以是同步。
在涉及到 IO
处理时通常都会遇到一个是同步还是异步的处理方式的选择问题。同步能够保证程序的可靠性,而异步可以提升程序的性能。小明自己去取书不管等着不等着迟早拿到书,指望小刚找到了送来,万一小刚忘了或者有急事忙别的了,那书就没了。
讨论
说实话,网上关于同步与异步、阻塞与非阻塞的文章多之又多,大部分是拷贝的,也有些写的非常好的。参考了许多,也借鉴了许多,也经过自己的思考。
同步与异步、阻塞与非阻塞之间确实有很多相似的地方,很容易混淆。wiki更是把异步与非阻塞画上了等号,更多的人还是认为他们是不同的。原因可能有很多,每个人的知识背景不同,设定的上下文也不同。
我的看法是:在IO中,根据上面同步异步的概念,也可以看出来同步与异步往往是通过阻塞非阻塞的形式来表达的,并且是通过一种中间处理机制来达到异步的效果。同步与异步往往是IO操作请求者和回应者之间在IO实际操作阶段的协作方式,而阻塞非阻塞更确切的说是一种自身状态,当前进程或者线程的状态。
在发出IO读请求后,阻塞IO会一直等待有数据可读,当有数据可读时,会等待数据从内核拷贝至系统进程;而非阻塞IO都会立即返回。至于数据怎么处理是程序进程自己的事情,无关同步和异步。
两种方式的组合
组合的方式当然有四种,分别是:同步阻塞、同步非阻塞、异步阻塞、异步非阻塞。
Java网络IO实现和IO模型
不同的操作系统上有不同的IO模型,《Unix网络编程卷》将unix上的IO模型分为5类:blocking I/O、nonblocking
I/O、I/O multiplexing (select and poll)、signal driven I/O (SIGIO)以及asynchronous
I/O (the POSIX aio_functions)。具体可参考《Unix网络编程卷1》6.2章节。
在windows上IO模型也是有5种:select 、WSAAsyncSelect、WSAEventSelect、Overlapped I/O
事件通知以及IOCP。具体可参考windows五种IO模型。
Java是平台无关的语言,在不同的平台上会调用底层操作系统的不同的IO实现,下面就来说一下Java提供的网络IO的工具和实现,为了扩大阻塞非阻塞的直观感受,我都使用了长连接。
(长连接: 在短信开发中,用到,象cmpp中提到过,就是不间断的发送测试连通性的包,以确认是否连接中断 如果中断,则继续连接;
短连接:连接完成,发送完消息后,就断开连接,下次在发消息的时候在次连接;)
阻塞IO
同步阻塞最常用的一种用法,使用也是最简单的,但是 I/O 性能一般很差,CPU
大部分在空闲状态。下面是一个简单的基于TCP的同步阻塞的Socket服务端例子:
1 @Test
2 public void testJIoSocket() throws
Exception
3 {
4 ServerSocket serverSocket = new
ServerSocket(10002);
5 Socket socket = null;
6 try
7 {
8 while (true)
9
{
10 socket = serverSocket.accept();
11
System.out.println("socket连接:" +
socket.getRemoteSocketAddress().toString());
12
BufferedReader in = new BufferedReader(new
InputStreamReader(socket.getInputStream()));
13
while(true)14 {
15 String readLine =
in.readLine();
16 System.out.println("收到消息" +
readLine);
17
if("end".equals(readLine))
18
{
19 break;20
}
21 //客户端断开连接22
socket.sendUrgentData(0xFF);
23 }
24
}
25 }
26 catch (SocketException se)
27
{28 System.out.println("客户端断开连接");
29 }
30
catch (IOException e)
31 {
32
e.printStackTrace();
33 }
34 finally
35
{
36 System.out.println("socket关闭:" +
socket.getRemoteSocketAddress().toString());
37
socket.close();
38 }
39 }
使用SocketTest作为客户端工具进行测试,同时开启2个客户端连接Server端并发送消息,如下图:
再看下后台的打印
socket连接:/127.0.0.1:54080收到消息hello!收到消息my name is client1
由于服务器端是单线程的,在第一个连接的客户端阻塞了线程后,第二个客户端必须等待第一个断开后才能连接。当输入“end”字符串断开客户端1,这时候看到后台继续打印:
socket连接:/127.0.0.1:54080收到消息hello!收到消息my name is
client1收到消息endsocket关闭:/127.0.0.1:54080socket连接:/127.0.0.1:54091收到消息hello!收到消息my
name is client2
所有的客户端连接在请求服务端时都会阻塞住,等待前面的完成。即使是使用短连接,数据在写入
OutputStream 或者从 InputStream 读取时都有可能会阻塞。这在大规模的访问量或者系统对性能有要求的时候是不能接受的。
阻塞IO + 每个请求创建线程/线程池
通常解决这个问题的方法是使用多线程技术,一个客户端一个处理线程,出现阻塞时只是一个线程阻塞而不会影响其它线程工作;为了减少系统线程的开销,采用线程池的办法来减少线程创建和回收的成本,模式如下图:
简单的实现例子如下,使用一个线程(Accptor)接收客户端请求,为每个客户端新建线程进行处理(Processor),线程池的我就不弄了:
public class MultithreadJIoSocketTest{
@Test
public void testMultithreadJIoSocket() throws Exception
{
ServerSocket serverSocket = new ServerSocket(10002);
Thread thread = new Thread(new Accptor(serverSocket));
thread.start();
Scanner scanner = new Scanner(System.in);
scanner.next();
}
public class Accptor
implements Runnable
{
private ServerSocket
serverSocket;
public Accptor(ServerSocket serverSocket)
{
this.serverSocket = serverSocket;
}
public void run()
{
while
(true)
{
Socket socket = null;
try
{
socket = serverSocket.accept();
if(socket != null)
{
System.out.println(“收到了socket:” +
socket.getRemoteSocketAddress()。toString());
Thread thread = new
Thread(new Processor(socket));
thread.start();
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
}
public class Processor implements
Runnable
{
private Socket socket;
public Processor(Socket socket)
{
this.socket =
socket;
}
@Override
public void
run()
{
try
{
BufferedReader in = new BufferedReader(new
InputStreamReader(socket.getInputStream()));
String
readLine;
while(true)
{
readLine =
in.readLine();
System.out.println(“收到消息” + readLine);
if(“end”.equals(readLine))
{
break;
}
//客户端断开连接
socket.sendUrgentData(0xFF);
Thread.sleep(5000);
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
catch (SocketException se)
{
System.out.println(“客户端断开连接”);
}
catch
(IOException e)
{
e.printStackTrace();
}
finally {
try
{
socket.close();
}
catch (IOException
e)
{
e.printStackTrace();
}
}
}
}}
使用2个客户端连接,这次没有阻塞,成功的收到了2个客户端的消息。
收到了socket:/127.0.0.1:55707收到了socket:/127.0.0.1:55708收到消息hello!收到消息hello!
在单个线程处理中,我人为的使单个线程read后阻塞5秒,就像前面说的,出现阻塞也只是在单个线程中,没有影响到另一个客户端的处理。
这种阻塞IO的解决方案在大部分情况下是适用的,在出现NIO之前是最通常的解决方案,Tomcat里阻塞IO的实现就是这种方式。但是如果是大量的长连接请求呢?不可能创建几百万个线程保持连接。再退一步,就算线程数不是问题,如果这些线程都需要访问服务端的某些竞争资源,势必需要进行同步操作,这本身就是得不偿失的。
非阻塞IO + IO multiplexing
Java从1.4开始提供了NIO工具包,这是一种不同于传统流IO的新的IO方式,使得Java开始对非阻塞IO支持;NIO并不等同于非阻塞IO,只要设置Blocking属性就可以控制阻塞非阻塞。至于NIO的工作方式特点原理这里一概不说,以后会写。模式如下图:
下面是简单的实现:
public class NioNonBlockingSelectorTest{
Selector selector;
private ByteBuffer receivebuffer =
ByteBuffer.allocate(1024);
@Test
public void
testNioNonBlockingSelector()
throws Exception
{
selector = Selector.open();
SocketAddress
address = new InetSocketAddress(10002);
ServerSocketChannel
channel = ServerSocketChannel.open();
channel.socket()。bind(address);
channel.configureBlocking(false);
channel.register(selector,
SelectionKey.OP_ACCEPT);
while(true)
{
selector.select();
Iterator iterator =
selector.selectedKeys()。iterator();
while (iterator.hasNext())
{
SelectionKey selectionKey = iterator.next();
iterator.remove();
handleKey(selectionKey);
}
}
}
private void
handleKey(SelectionKey selectionKey) throws IOException
{
ServerSocketChannel server = null;
SocketChannel client =
null;
if(selectionKey.isAcceptable())
{
server = (ServerSocketChannel)selectionKey.channel();
client = server.accept();
System.out.println(“客户端: ” +
client.socket()。getRemoteSocketAddress()。toString());
client.configureBlocking(false);
client.register(selector,
SelectionKey.OP_READ);
}
if(selectionKey.isReadable())
{
client =
(SocketChannel)selectionKey.channel();
receivebuffer.clear();
int count =
client.read(receivebuffer);
if (count > 0) {
String receiveText = new String( receivebuffer.array(),0,count);
System.out.println(“服务器端接受客户端数据--:” +
receiveText);
client.register(selector,
SelectionKey.OP_READ);
}
}
}
}
Java
NIO提供的非阻塞IO并不是单纯的非阻塞IO模式,而是建立在Reactor模式上的IO复用模型;在IO multiplexing
Model中,对于每一个socket,一般都设置成为non-blocking,但是整个用户进程其实是一直被阻塞的。只不过进程是被select这个函数阻塞,而不是被socket
IO给阻塞,所以还是属于非阻塞的IO.
这篇文章中把这种模式归为了异步阻塞,我其实是认为这是同步非阻塞的,可能看的角度不一样。
异步IO
Java1.7中提供了异步IO的支持,暂时还没有看过,所以以后再讨论。
网络IO优化
对于网络IO有一些基本的处理规则如下:
1.减少交互的次数。比如增加缓存,合并请求。
2.减少传输数据大小。比如压缩后传输、约定合理的数据协议。
3.减少编码。比如提前将字符转化为字节再传输。
4.根据应用场景选择合适的交互方式,同步阻塞,同步非阻塞,异步阻塞,异步非阻塞。
就说到这里吧,感觉有点乱,有些地方还是找不到更贴切的语言来描述。
相关推荐
Socket编程中的阻塞与非阻塞、同步与异步是两个独立的概念,它们涉及的是不同层面的操作机制。这里我们将详细探讨这两个概念以及I/O模型。 首先,同步与异步是客户端(C端)调用服务端(S端)时的行为模式。同步...
根据I/O操作的不同特性,可以将其分为四大类:同步阻塞IO、同步非阻塞IO、异步阻塞IO以及异步非阻塞IO。本文将详细介绍这四种不同的I/O模型,帮助读者理解它们之间的差异及应用场景。 #### 二、同步阻塞IO 同步阻塞...
同步与异步决定了客户端等待服务器响应的方式,而阻塞与非阻塞则决定了服务器处理I/O操作的策略。选择正确的组合可以显著提高系统的并发性和响应能力,从而提升用户体验。在设计网络应用程序时,需要根据具体需求...
总结起来,Java IO模型1涵盖了同步与异步、阻塞与非阻塞的基本概念,以及五种不同的IO处理方式,每种都有其适用场景和优缺点,开发者需要根据具体需求选择合适的IO模型。对于高并发、高效率的网络应用,通常会选择非...
在Java中,NIO(New IO)就是一种支持异步IO的方式。NIO引入了选择器(Selector)和通道(Channel)的概念,允许单线程处理多个通道的IO事件。例如,服务器可以注册多个客户端连接到同一个选择器,当有数据到达时,...
- **使用场景**:适用于复杂的异步编程场景,如异步IO操作、异步网络请求等。 #### 四、案例分析 以下是一个简单的synchronized关键字使用的示例: ```java package test.thread; public class SynTest { // ...
无论是阻塞还是非阻塞模式,同步IO的特点在于它不提供任何完成通知,即无论是否阻塞,都需要通过某种方式(如轮询)检查IO操作是否完成。 - **阻塞同步IO**:典型的例子是传统的文件读写操作,这些操作在完成之前...
在 Linux 中,IO 模型可以分为阻塞 IO、非阻塞 IO、异步 IO 等几种。 1. 阻塞 IO 模型 阻塞 IO 模型是最基本的 IO 模型。在这种模型中,进程发起 IO 请求后,会一直等待直到 IO 操作完成。在这个过程中,进程会被...
在网络编程中,IO模型主要包括同步阻塞IO、同步非阻塞IO、I/O复用、信号驱动IO和异步非阻塞IO。其中,同步阻塞IO是最常见的模型,程序在等待数据准备好时会被挂起。同步非阻塞IO则允许程序在数据未准备好时返回并...
### Linux异步IO详解 #### 引言 在Linux环境下,输入/输出(I/O)操作是系统资源管理和数据交互的核心部分。传统上,Linux采用的最常见I/O模型是同步I/O,其中应用程序在发出请求后会阻塞,直至请求完成。然而,...
非阻塞的响应式IO模型,如WebClient采用的,借鉴了如Netty这样的高性能网络通信框架。在这一模型中,请求处理和线程交互更为高效,通过事件驱动和回调机制,可以显著提高并发处理能力,而不仅仅是单一请求的响应速度...
在这个"java-demo"项目中,我们可以深入学习Java技术,特别是关于多线程、IO流以及两种不同的IO模型——阻塞IO(BIO)和非阻塞IO。此外,还涉及到Netty框架的应用,这是一个高性能、异步事件驱动的网络应用框架,常...
在IT领域,文件的输入/输出(Input/Output, IO)操作是计算机系统与外部存储交互的基本方式。在处理大量数据或需要高效响应时间的应用中,异步IO操作显得尤为重要。本文将深入探讨异步读取、异步写入以及文件删除等...
9.1 阻塞与非阻塞、同步与异步IO 阻塞IO和非阻塞IO的区别在于当设备或数据未准备好时,如何处理应用程序的请求。在阻塞IO中,如果数据未就绪,应用程序会等待直到数据准备完毕。相反,非阻塞IO则立即返回,告知数据...
NIO(New IO)是Java 1.4引入的扩展,提供了非阻塞I/O操作,适用于高性能、低延迟的系统。NIO2进一步增强了异步I/O能力,引入了文件系统监控和Path API,使得文件操作更加简洁高效。 Util则是“实用工具”的缩写,...
Java NIO提供了与传统I/O不同的I/O工作方式,它引入了缓冲区(Buffer)和通道(Channel)的概念,支持非阻塞式IO。相对于传统的IO,NIO支持面向缓冲区的、基于通道的I/O操作。NIO还引入了选择器(Selector)的概念,允许...
- **2.3 AIO(异步非阻塞IO)** - **工作原理**:AIO是JDK 1.7引入的新特性,它允许应用程序发起异步的读写请求,并通过CompletionHandler来接收完成的通知。 - **优缺点**: - **优点**:进一步提高了系统的并发...
相反,异步非阻塞模式允许进程在等待数据时立即返回,继续处理其他任务,当数据准备就绪时,操作系统通过事件通知(如IO完成端口或信号量)来唤醒进程。这种方式提高了系统的并发处理能力,更适用于高并发的聊天室...
在这个“基于Java非阻塞IO技术实现石头剪刀布游戏设计”的项目中,开发者利用了Java NIO(New IO)库来创建一个高效、低延迟的网络通信环境,用于玩家之间的游戏交互。以下将详细介绍Java非阻塞I/O以及如何将其应用...
### Golang将多路复用异步IO转成阻塞IO的方法详解 #### 前言 在Golang中处理网络通信时,开发者通常利用语言内置的高效并发机制(如goroutines和channels)来实现非阻塞IO或多路复用IO。然而,在某些场景下,我们...