转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7723174.html

  前面讲到:Java IO编程全解(二)——传统的BIO编程

  为了解决同步阻塞I/O面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化,后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数M:线程池最大线程数N的比例关系,其中M可以远远大于N,通过线程池可以灵活的调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。

  下面,我们结合连接模型图和源码,对伪异步I/O进行分析,看它是否能够解决同步阻塞I/O面临的问题。

1. 伪异步I/O模型图

  采用线程池和任务队列可以实现一种叫做伪异步的I/O通信框架,它的模型图如下所示。

  当有新的客户端接入的时候,将客户端的Socket封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK的线程池维护一个消息队列和N个活跃线程对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数。因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。

图1 伪异步I/O服务端通信模型(M:N)

  下面我们依然采用时间服务器程序,将其改造成为伪异步I/O时间服务器,然后通过对代码进行分析,找出其弊端。

2.伪异步式I/O创建的TimeServer源码分析

package joanna.yan.poio;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 伪异步式I/O
* @author Joanna.Yan
* @date 2017年10月24日上午10:16:10
*/
public class TimeServer { public static void main(String[] args) {
int port=9090;
if(args!=null&&args.length>0){
try {
port=Integer.valueOf(args[0]);
} catch (Exception e) {
// 采用默认值
}
} ServerSocket server=null;
try {
server=new ServerSocket(port);
System.out.println("The time server is start in port :"+ port);
Socket socket=null;
//创建一个时间服务器类的线程池
TimeServerHandlerExecutePool singleExecutor=new
TimeServerHandlerExecutePool(50, 10000);//创建I/O任务 while(true){
socket=server.accept();
//当接收到新的客户端连接时,将请求Socket封装成一个Task,然后调用execute方法执行。从而避免了每个请求接入都创建一个新的线程。
singleExecutor.execute(new TimeServerHandler(socket));
}
} catch (IOException e) {
e.printStackTrace();
}finally{
if(server!=null){
try {
System.out.println("The time server close");
server=null;
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
package joanna.yan.poio;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 由于线程池和消息队列都是有界的,因此,无论客户端并发连接数多大,它都不会导致线程个数过于膨胀或者内存溢出,
* 相比于传统的一连接一线程模型,是一种改良。
* @author Joanna.Yan
* @date 2017年10月24日下午2:39:49
*/
public class TimeServerHandlerExecutePool {
private ExecutorService executor; public TimeServerHandlerExecutePool(int maxPoolSize,int queueSize){
executor=new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),
maxPoolSize, 120L, TimeUnit.SECONDS,
new ArrayBlockingQueue<java.lang.Runnable>(queueSize)); } public void execute(java.lang.Runnable task){
executor.execute(task);;
}
}
package joanna.yan.poio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Date; public class TimeServerHandler implements Runnable{
private Socket socket; public TimeServerHandler(Socket socket) {
this.socket = socket;
} @Override
public void run() {
BufferedReader in=null;
PrintWriter out=null; try {
in=new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out=new PrintWriter(this.socket.getOutputStream(), true);
String currentTime=null;
String body=null;
while(true){
body=in.readLine();
if(body==null){
break;
}
System.out.println("The time server receive order:"+body);
//如果请求消息为查询时间的指令"QUERY TIME ORDER"则获取当前最新的系统时间。
currentTime="QUERY TIME ORDER".equalsIgnoreCase(body) ?
new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
out.println(currentTime);
}
} catch (IOException e) {
e.printStackTrace();
}finally{
if(in!=null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out!=null){
out.close();
out=null;
}
if(this.socket!=null){
try {
this.socket.close();
this.socket=null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

  伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。但是由于它底层的通信依然采用同步阻塞模型,因此无法从根本上解决问题。

3.伪异步I/O弊端分析

  要对伪异步I/O的弊端进行深入分析,首先我们看两个Java同步I/O的API说明。随后我们结合代码进行详细分析。

  请注意加粗斜体字部分的API说明,当对Socket的输入流进行读取操作的时候,它会一直阻塞下去,直到发生如下三种事件。

  • 有数据可读;
  • 可用数据已经读取完毕;
  • 发生空指针或者I/O异常。

  这意味着当对方发送请求或者应答消息比较缓慢、或者网络传输较慢时,读取输入流一方的通信线程将被长时间阻塞,如果对方要60s才能够将数据发送完成,读取一方的I/O线程也将会被同步阻塞60s,在此期间,其他接入消息只能在消息队列中排队。

  下面我们接着对输出流进行分析,还是看JDK I/O类库输出流的API文档,然后结合文档说明进行故障分析。

  当调用OutputStream的write方法写输出流的时候,它将会被阻塞,直到要发送的字节全部写入完毕,或者发生异常。学习过TCP/IP相关知识的人都知道,当消息的接收方处理缓慢的时候,将不能及时地从TCP缓冲区读取数据,这将会导致发送方的TCP window size不断减小,直到为0,双方处于Keep-Alive状态,消息发送方将不能再向TCP缓冲区写入消息,这是如果采用的是同步阻塞I/O,write操作将会被无限期阻塞,直到TCP window size大于0或者发生I/O异常。

  通过对输入和输出流的API文档进行分析,我们了解到读和写操作都是同步阻塞的,阻塞的时间取决于对方I/O线程的处理速度和网络I/O传输速度。本质上来讲,我们无法保证生产环境的网络状况和对端的应用程序能够足够快,如果我们的应用程序依赖对方的处理速度,它的可靠性就非常差。

  伪异步I/O实际上仅仅只是对之前I/O线程模型的一个简单优化,它无法从根本上解决同步I/O导致的通信线程阻塞问题。下面我们就简单分析下如果通信对方返回应答时间过长,会引起的级联故障。

  1.  服务端处理缓慢,返回应答消息耗费60s,平时只需要10ms。
  2. 采用伪异步I/O的线程正在读取故障服务节点的响应,由于读取输入流是阻塞的,因此,它将会被同步阻塞60s。
  3. 假如所有的可用线程都被故障服务器阻塞,那后续所有的I/O消息都将在队里中排队。
  4. 由于线程池采用阻塞队里实现,当队列积满之后,后续入队的操作将被阻塞。
  5. 由于前端只有一个Accptor线程接收客户端接入,它被阻塞在线程池的同步阻塞队列之后,新的客户端请求消息将被拒绝,      客户端会发生大量的连接超时。
  6. 由于几乎所有的连接都超时,调用者会认为系统已经崩溃,无法接收新的请求消息。

  那么这个问题如何解决?后面的NIO将给出答案。

Java IO编程全解(四)——NIO编程

如果此文对您有帮助,微信打赏我一下吧~

Java IO编程全解(三)——伪异步IO编程的更多相关文章

  1. Java IO编程全解(四)——NIO编程

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7793964.html 前面讲到:Java IO编程全解(三)——伪异步IO编程 NIO,即New I/O,这 ...

  2. Java IO编程全解(五)——AIO编程

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7794151.html 前面讲到:Java IO编程全解(四)--NIO编程 NIO2.0引入了新的异步通道的 ...

  3. Java IO编程全解(六)——4种I/O的对比与选型

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7804185.html 前面讲到:Java IO编程全解(五)--AIO编程 为了防止由于对一些技术概念和术语 ...

  4. Java IO编程全解(二)——传统的BIO编程

    前面讲到:Java IO编程全解(一)——Java的I/O演进之路 网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口 ...

  5. Java IO编程全解(一)——Java的I/O演进之路

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7419117.html JDK1.4之前的早期版本,Java对I/O的支持并不完善,开发人员在开发高性能I/O ...

  6. 伪异步IO理解

    伪异步IO实在堵塞IO的基础上将每个client发送过来的请求由新创建的线程来处理改进为用线程池来处理.因此避免了为每个client请求创建一个新线程造成的资源耗尽问题. 来看一下伪异步IO的服务端代 ...

  7. netty权威指南学习笔记一——NIO入门(2)伪异步IO

    在上一节我们介绍了四种IO相关编程的各个特点,并通过代码进行复习了传统的网络编程代码,伪异步主要是引用了线程池,对BIO中服务端进行了相应的改造优化,线程池的引入,使得我们在应对大量客户端请求的时候不 ...

  8. 伪异步IO

    针对传统的BIO编程,当客户端数量一直增加的情况下,可能会导致服务器直接奔溃掉,进而出现了一种伪异步IO的线程方式. 先看一下代码: 看一下server端的代码: 其中使用了自定义的一个线程池Hand ...

  9. 理论铺垫:阻塞IO、非阻塞IO、IO多路复用/事件驱动IO(单线程高并发原理)、异步IO

    完全来自:http://www.cnblogs.com/alex3714/articles/5876749.html 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同 ...

随机推荐

  1. 关于DbContext能不能单次请求内唯一?DbContex需不需要主动释放?欢迎各路大侠来“参战”!

    基于前篇文章<HiBlogs>重写笔记[1]--从DbContext到依赖注入再到自动注入园友@Flaming丶淡蓝@ 吴瑞祥 提出了讨论和质疑,吓得我连夜查询资料(玩笑~). 本来文章的 ...

  2. [js高手之路] html5 canvas系列教程 - 图片操作(drawImage,clip,createPattern)

    接着上文[js高手之路] html5 canvas系列教程 - 文本样式(strokeText,fillText,measureText,textAlign,textBaseline)继续,本文介绍的 ...

  3. GitHub使用(一) - 新建个人网站

    1.首先进入“仓库Repositories”,点击“新建New”.

  4. C#的"?"修饰符和"??"运算符

    一.  ?  可空类型修饰符 “?”用来修饰为空的值类型,在加上“?”修饰符后,值类型也可以为空了,如: public int? CommandTimeout { get; }: var prop = ...

  5. 实战系列之 Node.js 玩转 Java

    这些年以来,Node.js的兴起,JavaScript已经从当年的“世界最被误解的语言”变成了“世界最流行的语言”.且其发展之势,从语言本身的进化,库和包的增长,工具支持的完善,star项目和领域解决 ...

  6. Ionic3学习笔记(四)修改返回按钮文字、颜色

    本文为原创文章,转载请标明出处 目录 修改返回按钮文字 修改返回按钮颜色 1. 修改返回按钮文字 参考官网 Ionic API---Config 文档 可在 ./src/app/app.module. ...

  7. POJ 2472 106 miles to Chicago(Dijstra变形——史上最坑的最长路问题)

    题目链接 :http://poj.org/problem?id=2472 Description In the movie "Blues Brothers", the orphan ...

  8. windows phone 模拟器

    window phone 模拟器启动报错 修改Bios设置,我的是yoga pro 2,只修改 即可.启动成功

  9. ASP.NET没有魔法——ASP.NET MVC 与数据库大集合

    ASP.NET没有魔法——ASP.NET与数据库 ASP.NET没有魔法——ASP.NET MVC 与数据库之MySQL ASP.NET没有魔法——ASP.NET MVC 与数据库之ORM ASP.N ...

  10. 固定GridView标题栏,冻结列功能实现

    <%@ Page Language="C#" %> <%@ Import Namespace="System.Data" %> < ...