为了解决同步阻塞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源码分析

packagejoanna.yan.poio;importjava.io.IOException;importjava.net.ServerSocket;importjava.net.Socket;/*** 伪异步式I/O

*@authorJoanna.Yan

* @date 2017年10月24日上午10:16:10*/

public classTimeServer {public static voidmain(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=newServerSocket(port);

System.out.println("The time server is start in port :"+port);

Socket socket=null;//创建一个时间服务器类的线程池

TimeServerHandlerExecutePool singleExecutor=newTimeServerHandlerExecutePool(50, 10000);//创建I/O任务

while(true){

socket=server.accept();//当接收到新的客户端连接时,将请求Socket封装成一个Task,然后调用execute方法执行。从而避免了每个请求接入都创建一个新的线程。

singleExecutor.execute(newTimeServerHandler(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();

}

}

}

}

}

packagejoanna.yan.poio;importjava.util.concurrent.ArrayBlockingQueue;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.ThreadPoolExecutor;importjava.util.concurrent.TimeUnit;/*** 由于线程池和消息队列都是有界的,因此,无论客户端并发连接数多大,它都不会导致线程个数过于膨胀或者内存溢出,

* 相比于传统的一连接一线程模型,是一种改良。

*@authorJoanna.Yan

* @date 2017年10月24日下午2:39:49*/

public classTimeServerHandlerExecutePool {privateExecutorService executor;public TimeServerHandlerExecutePool(int maxPoolSize,intqueueSize){

executor=newThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),

maxPoolSize,120L, TimeUnit.SECONDS,new ArrayBlockingQueue(queueSize));

}public voidexecute(java.lang.Runnable task){

executor.execute(task);;

}

}

packagejoanna.yan.poio;importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.PrintWriter;importjava.net.Socket;importjava.util.Date;public class TimeServerHandler implementsRunnable{privateSocket socket;publicTimeServerHandler(Socket socket) {this.socket =socket;

}

@Overridepublic voidrun() {

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导致的通信线程阻塞问题。下面我们就简单分析下如果通信对方返回应答时间过长,会引起的级联故障。

服务端处理缓慢,返回应答消息耗费60s,平时只需要10ms。

采用伪异步I/O的线程正在读取故障服务节点的响应,由于读取输入流是阻塞的,因此,它将会被同步阻塞60s。

假如所有的可用线程都被故障服务器阻塞,那后续所有的I/O消息都将在队里中排队。

由于线程池采用阻塞队里实现,当队列积满之后,后续入队的操作将被阻塞。

由于前端只有一个Accptor线程接收客户端接入,它被阻塞在线程池的同步阻塞队列之后,新的客户端请求消息将被拒绝,      客户端会发生大量的连接超时。

由于几乎所有的连接都超时,调用者会认为系统已经崩溃,无法接收新的请求消息。

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

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

java 异步模型_Java IO编程全解(三)——伪异步IO编程相关推荐

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

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

  2. abb焊接机器人编程全解_ABB机器人的离线编程与仿真之原程序详解!!!——ABB机器人...

    在工作站配置中所使用的机器人是ABB机器人 IRB1410型机器人,并且事先已经使用Smart组件构建完成机器人所使用的工具的夹取和放置的动态效果. 程序注释: MODULE MainMoudle 程 ...

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

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

  4. Java内存模型(JMM)详解-可见性volatile

    这里写自定义目录标题 Java内存模型(JMM)详解-可见性 什么是JMM JMM存在的意义 为什么示例demo中不会打印 i 的值 如何解决可见性问题 **深入理解JMM内存模型** JAVA内存模 ...

  5. 全方位地介绍JavaScript开发中的各个主题《JavaScript编程全解》(好书分享更新中)

    JavaScript编程全解 作者: [日]井上诚一郎 / [日]土江拓郎 / [日]滨边将太 出版社: 人民邮电出版社 译者: 陈筱烟 内容简介  · · · · · · 本书全方位地介绍了Java ...

  6. java虚拟机类加载机制与反射全解

    java虚拟机类加载机制与反射全解 引子: 开门见山,先来个经典面试题:(如果你已经懂了,那么你可以离开了,如果你一脸懵逼,那么请好好看本文,理解透彻很有好处!) class SingleTon {p ...

  7. python基础系列教程——python面向对象编程全解

    全栈工程师开发手册 (作者:栾鹏) python教程全解 python面向对象编程全解. 面向对象技术简介 一个类占有一个独立的空间,类中的属性叫做类变量,类中的函数,叫做类的方法. 类(Class) ...

  8. Sql Server函数全解三数据类型转换函数和文本图像函数

    原文:Sql Server函数全解<三>数据类型转换函数和文本图像函数 一:数据类型转换函数 在同时处理不同数据类型的值时,SQL Server一般会自动进行隐士类型转换.对于数据类型相近 ...

  9. 啦啦外卖45.9全解+三端小程序,修复坑位正常使用截图预览

    啦啦外卖45.9全解+三端小程序,修复坑位正常使用截图预览,全部已知或隐藏问题已解决

最新文章

  1. SQL2008代理作业出现错误: c001f011维护计划创建失败的解决方法
  2. Python 爬虫篇-爬取web页面所有可用的链接实战演示,展示网页里所有可跳转的链接地址
  3. linux 换行符_一个linux帮你做高效数据统计
  4. java集合(4)-Set集合
  5. 虚拟化安全防护系统部署在安全服务器上,虚拟化安全防护-安天 智者安天下
  6. windows node.js安装以及启动过程
  7. java ajax无权限跳转_如何在ajax权限判断后跳转?
  8. 24种常用HTML常用实例
  9. freecodecamp_freeCodeCamp论坛的未来
  10. Enjoy模板里使用layui模板引擎laytpl
  11. Advanced Bash Sell Scripting学习笔记1
  12. html事件中写js,html标签中绑定触发事件与js中绑定触发事件写法上的区别
  13. Codeforces 295 (Div.1)
  14. PHP QQ网页三方登录
  15. Koo叔说Shader—UV旋转
  16. Linux下vi命令编辑器,编辑 ,保存和退出
  17. css鼠标划过时的一些小特效
  18. 工地上的这些工种工资差距很大,你知道是哪些工种吗?
  19. 【统计学】统计学基础
  20. CentOS 7上编译安装PHP 8.1及Nginx 配置支持PHP

热门文章

  1. C++ 窗口设计 实践项目2 个人所得税计算器
  2. 面经分享 | 小白菜的2020秋招经历分享记录(Java开发方向)
  3. Manacher马拉车算法求最长回文子串
  4. 报错信息:An Error Was Encountered
  5. ssb的matlab仿真,基于matlab软件仿真——单边带、双边带调制解调程序和Simulink建模仿真...
  6. 电流检测放大器在高端电流监测中的应用
  7. 基于POI框架操作Excel文件
  8. scrollTo与scrollBy用法以及TouchSlop与VelocityTracker解析
  9. Codeforces Round #470 D Perfect Security
  10. mysql强力推荐启用binlog