Java进阶知识点5:服务端高并发的基石 - NIO与Reactor模式以及AIO与Proactor模式

2023-11-14

一、背景

要提升服务器的并发处理能力,通常有两大方向的思路。

1、系统架构层面。比如负载均衡、多级缓存、单元化部署等等。

2、单节点优化层面。比如修复代码级别的性能Bug、JVM参数调优、IO优化等等。

一般来说,系统架构的合理程度,决定了系统在整体性能上的伸缩性(高伸缩性,简而言之就是可以很任性,性能不行就加机器,加到性能足够为止);而单节点在性能上的优化程度,决定了单个请求的时延,以及要达到期望的性能,所需集群规模的大小。两者双管齐下,才能快速构建出性能良好的系统。

今天,我们就聊聊在单节点优化层面最重要的IO优化。之所以IO优化最重要,是因为IO速度远低于CPU和内存,而不够良好的软件设计,常常导致CPU和内存被IO所拖累,如何摆脱IO的束缚,充分发挥CPU和内存的潜力,是性能优化的核心内容。

而CPU和内存又是如何被IO所拖累的呢?这就从Java中几种典型的IO操作模式说起。

二、Java中的典型IO操作模式

2.1 同步阻塞模式

Java中的BIO风格的API,都是该模式,例如:

Socket socket = getSocket();
socket.getInputStream().read(); //读不到数据誓不返回

该模式下,最直观的感受就是如果IO设备暂时没有数据可供读取,调用API就卡住了,如果数据一直不来就一直卡住。

2.2 同步非阻塞模式

Java中的NIO风格的API,都是该模式,例如:

SocketChannel socketChannel = getSocketChannel(); //获取non-blocking状态的Channel
socketChannel.read(ByteBuffer.allocate(4)); //读不到数据就算了,立即返回0告诉你没有读到

该模式下,通常需要不断调用API,直至读取到数据,不过好在函数调用不会卡住,我想继续尝试读取或者先去做点其他事情再来读取都可以。

2.3 异步非阻塞模式

Java中的AIO风格的API,都是该模式,例如:

AsynchronousSocketChannel asynchronousSocketChannel = getAsynchronousSocketChannel();
asynchronousSocketChannel.read(ByteBuffer.allocate(4), null, new CompletionHandler<Integer, Object>() {
    @Override
    public void completed(Integer result, Object attachment) {
        //读不到数据不会触发该回调来烦你,只有确实读取到数据,且把数据已经存在ByteBuffer中了,API才会通过此回调接口主动通知您
    }
    @Override
    public void failed(Throwable exc, Object attachment) {
    }
});

该模式服务最到位,除了会让编程变的相对复杂以外,几乎无可挑剔。

 2.4 小结

对于IO操作而言,同步和异步的本质区别在于API是否会将IO就绪(比如有数据可读)的状态主动通知你。同步意味着想要知道IO是否就绪,必须发起一次询问,典型的一问一答,如果回答是没有就绪,那你还得自己不断询问,直到答案是就绪为止。异步意味着,IO就绪后,API将主动通知你,无需你不断发起询问,这通常要求调用API时传入通知的回调接口。

阻塞和非阻塞的本质区别在于IO操作因IO未就绪不能立即完成时,API是否会将当前线程挂起。阻塞意味着API会一直等待IO就绪后,完成本次IO操作才返回,在此之前调用该API的用户线程将一直挂起,无法进行其他计算处理。非阻塞意味着API会立即返回,而不是等待IO就绪,用户可以立即再次获得线程的控制权,可以使用该线程进行其他计算处理。

那有没有异步阻塞模式呢?如果API支持异步,相当于API说:“你玩去吧,我准备好了通知你”,但是你还是傻乎乎地不去玩,原地等待API做完后的通知。这通常是因为本次IO操作很重要,拿不到结果业务流程根本无法继续,所以为了编程上的简单起见,还是乖乖等吧。可见异步阻塞模式更多的是出于业务流程控制和简化编码难度的考虑,由业务代码自主形成的,Java语言不会特别为你准备异步阻塞IO的API。

三、分离快与慢

3.1 BIO的局限

CPU和内存是高速设备,磁盘、网络等IO设备是低速设备,在Java编程语言中,对CPU和内存的使用被抽象为对线程、栈、堆的使用,对IO设备的使用被抽象为IO相关的API调用。

显然,如果使用BIO风格的IO API,由于其同步阻塞特性,会导致IO设备未就绪时,线程挂起,该线程无法继续使用CPU和内存,直至IO就绪。由于IO设备的速度远低于CPU和内存,所以使用BIO风格的API时,有极大的概率会让当前线程长时间挂起,这就形成了CPU和内存资源被IO所拖累的情况。

作为服务端应用,会面临大量客户端向服务端发起连接请求的场景,每个连接对服务端而言,都意味着需要进行后续的网络IO读取,IO读取完成后,才能获得完整的请求内容,进而才能再进行一些列相关计算处理获得请求结果,最后还要将结果通过网络IO回写给客户端。使用BIO的编码风格,通常是同一个线程全程负责一个连接的IO读取、数据处理和IO回写,该线程绝大部分时间都可能在等待IO就绪,只有极少时间在真正利用CPU资源。

而此时服务器要想同时处理大量客户端连接,后端就同时开启与并发连接数量相应的线程。线程是操作系统的宝贵资源,而且每开启一个操作系统线程,Java还会消耗-Xss指定的线程堆栈大小的堆外内存,如果同时存在大量线程,操作系统调度线程的开销也会显著增加,导致服务器性能快速下降。所以此时服务器想要支持上万乃至几十万的高并发连接,可谓难上加难。

3.2 NIO的突破

3.2.1 突破思路

由于NIO的非阻塞特性,决定了IO未就绪时,线程可以不必挂起,继续处理其他事情。这就为分离快与慢提供了可能,高速的CPU和内存可以不必苦等IO交互,一个线程也不必局限于只为一个IO连接服务。这样,就让用少量的线程处理海量IO连接成为了可能。

3.2.2 思路落地

虽然我们看到了曙光,但是要将这个思路落地还需解决掉一些实际的问题。

a)当IO未就绪时,线程就释放出来,转而为其他连接服务,那谁去监控这个被抛弃IO的就绪事件呢?

b)IO就绪了,谁又去负责将这个IO分配给合适的线程继续处理呢?

为了解决第一个问题,操作系统提供了IO多路复用器(比如Linux下的select、poll和epoll),Java对这些多路复用器进行了封装(一般选用性能最好的epoll),也提供了相应的IO多路复用API。NIO的多路复用API典型编程模式如下:

// 开启一个ServerSocketChannel,在8080端口上监听
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress("0.0.0.0", 8080));
// 创建一个多路复用器
Selector selector = Selector.open();
// 将ServerSocketChannel注册到多路复用器上,并声明关注其ACCEPT就绪事件
server.register(selector, SelectionKey.OP_ACCEPT);
while (selector.select() != 0) {
    // 遍历所有就绪的Channel关联的SelectionKey
    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
    while (iterator.hasNext()) {
        SelectionKey key = iterator.next();
        // 如果这个Channel是READ就绪
        if (key.isReadable()) {
            // 读取该Channel
            ((SocketChannel) key.channel()).read(ByteBuffer.allocate(10));
        }
        if (key.isWritable()) {
            //... ...
        }
        // 如果这个Channel是ACCEPT就绪
        if (key.isAcceptable()) {
            // 接收新的客户端连接
            SocketChannel accept = ((ServerSocketChannel) key.channel()).accept();
            // 将新的Channel注册到多路复用器上,并声明关注其READ/WRITE就绪事件
            accept.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
        }
        // 删除已经处理过的SelectionKey
        iterator.remove();
    }
}

IO多路复用API可以实现用一个线程,去监控所有IO连接的IO就绪事件。

第二个问题在上面的代码中其实也得到了“解决”,但是上面的代码是使用监控IO就绪事件的线程来完成IO的具体操作,如果IO操作耗时较大(比如读操作就绪后,有大量数据需要读取),那么会导致监控线程长时间为某个具体的IO服务,从而导致整个系统长时间无法感知其他IO的就绪事件并分派IO处理任务。所以生产环境中,一般使用一个Boss线程专门用于监控IO就绪事件,一个Work线程池负责具体的IO读写处理。Boss线程检测到新的IO就绪事件后,根据事件类型,完成IO操作任务的分配,并将具体的操作交由Work线程处理。这其实就是Reactor模式的核心思想。

3.2.3 Reactor模式

如上所述,Reactor模式的核心理念在于:

a)依赖于非阻塞IO。

b)使用多路复用器监管海量IO的就绪事件。

c)使用Boss线程和Work线程池分离IO事件的监测与IO事件的处理。

Reactor模式中有如下三类角色:

a)Acceptor。用户处理客户端连接请求。Acceptor角色映射到Java代码中,即为SocketServerChannel。

b)Reactor。用于分派IO就绪事件的处理任务。Reactor角色映射到Java代码中,即为使用多路复用器的Boss线程。

c)Handler。用于处理具体的IO就绪事件。(比如读取并处理数据等)。Handler角色映射到Java代码中,即为Worker线程池中的每个线程。

Acceptor的连接就绪事件,也是交由Reactor监管的,有些地方为了分离连接的建立和对连接的处理,为将Reactor分离为一个主Reactor,专门用户监管连接相关事件(即SelectionKey.OP_ACCEPT),一个从Reactor,专门用户监管连接上的数据相关事件(即SelectionKey.OP_READ 和SelectionKey.OP_WRITE)。

关于Reactor的模型图,网上一搜一大把,我就不献丑了。相信理解了它的核心思想,图自然在心中。关于Reactor模式的应用,可以参见著名NIO编程框架Netty,其实有了Netty之后,一般都直接使用Netty框架进行服务端NIO编程。

3.3 AIO的更进一步

3.3.1 AIO得天独厚的优势

你很容易发现,如果使用AIO,NIO突破时所面临的落地问题似乎天然就不存在了。因为每一个IO操作都可以注册回调函数,天然就不需要专门有一个多路复用器去监听IO就绪事件,也不需要一个Boss线程去分配事件,所有IO操作只要一完成,就天然会通过回调进入自己的下一步处理。

而且,更让人惊喜的是,通过AIO,连NIO中Work线程去读写数据的操作都可以省略了,因为AIO是保证数据真正读取/写入完成后,才触发回调函数,用户都不必关注IO操作本身,只需关注拿到IO中的数据后,应该进行的业务逻辑。

简而言之,NIO的多路复用器,是通知你IO就绪事件,AIO的回调是通知你IO完成事件。AIO做的更加彻底一些。这样在某些平台上也会带来性能上的提升,因为AIO的IO读写操作可以交由操作系统内核完成,充分发挥内核潜能,减少了IO系统调用时用户态与内核态间的上下文转换,效率更高。

(不过遗憾的是,Linux内核的AIO实现有很多问题(不在本文讨论范畴),性能在某些场景下还不如NIO,连Linux上的Java都是用epoll来模拟AIO,所以Linux上使用Java的AIO API,只是能体验到异步IO的编程风格,但并不会比NIO高效。综上,Linux平台上的Java服务端编程,目前主流依然采用NIO模型。)

使用AIO API典型编程模式如下:

//创建一个Group,类似于一个线程池,用于处理IO完成事件
AsynchronousChannelGroup group = AsynchronousChannelGroup.withCachedThreadPool(Executors.newCachedThreadPool(), 32);
//开启一个AsynchronousServerSocketChannel,在8080端口上监听
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group);
server.bind(new InetSocketAddress("0.0.0.0", 8080));
//接收到新连接
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
    //新连接就绪事件的处理函数
    @Override
    public void completed(AsynchronousSocketChannel result, Object attachment) {
        result.read(ByteBuffer.allocate(4), attachment, new CompletionHandler<Integer, Object>() {
            //读取完成事件的处理函数
            @Override
            public void completed(Integer result, Object attachment) {
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
            }
        });
    }
    @Override
    public void failed(Throwable exc, Object attachment) {
    }
});

3.3.2 Proactor模式

Java的AIO API其实就是Proactor模式的应用。

也Reactor模式类似,Proactor模式也可以抽象出三类角色:

a)Acceptor。用户处理客户端连接请求。Acceptor角色映射到Java代码中,即为AsynchronousServerSocketChannel。

b)Proactor。用于分派IO完成事件的处理任务。Proactor角色映射到Java代码中,即为API方法中添加回调参数。

c)Handler。用于处理具体的IO完成事件。(比如处理读取到的数据等)。Handler角色映射到Java代码中,即为AsynchronousChannelGroup 中的每个线程。

可见,Proactor与Reactor最大的区别在于:

a)无需使用多路复用器。

b)Handler无需执行具体的IO操作(比如读取数据或写入数据),而是只执行IO数据的业务处理。

四、总结

1、Java中的IO有同步阻塞、同步非阻塞、异步非阻塞三种操作模式,分别对应BIO、NIO、AIO三类API风格。

2、BIO需要保证一个连接一个线程,由于线程是操作系统宝贵资源,不可开过多,所以BIO严重限制了服务端可承载的并发连接数量。

3、使用NIO特性,辅以Reactor编程模式,是Java在Linux下实现服务器端高并发能力的主流方式。

4、使用AIO特性,辅以Proactor编程模式,在其他平台上(比如Windows)能够获得比NIO更高的性能。

 

 

 

 

转载于:https://www.cnblogs.com/itZhy/p/7727569.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java进阶知识点5:服务端高并发的基石 - NIO与Reactor模式以及AIO与Proactor模式 的相关文章

  • 菜单未显示在应用程序中

    由于某种原因 我的操作菜单在我的 Android Studio 应用程序中消失了 我正在按照教程学习如何创建 Android 应用程序 但最终遇到了这个问题 我正在使用 atm 的教程 http www raywenderlich com
  • Java 中的 XPath 节点集

    我在 eclipse 中有这段代码 NodeSet nodes NodeSet xPath evaluate expression inputSource XPathConstants NODESET 它给我 NodeSet 上的编译时错误
  • 如何使用 FileChannel 将一个文件的内容附加到另一个文件的末尾?

    File a txt好像 ABC File d txt好像 DEF 我正在尝试将 DEF 附加到 ABC 所以a txt好像 ABC DEF 我尝试过的方法总是完全覆盖第一个条目 所以我总是最终得到 DEF 这是我尝试过的两种方法 File
  • ElasticBeanstalk Java,Spring 活动配置文件

    我正在尝试通过 AWS ElasticBeanstalk 启动 spring boot jar 一切正常 配置文件为 默认 有谁知道如何为 java ElasticBeanstalk 应用程序 不是 tomcat 设置活动配置文件 spri
  • 如何测试 JUnit 测试的 Comparator?

    我需要测试 Compare 方法 但我对如何测试感到困惑 我可以看看该怎么做吗 public class MemberComparator implements Comparator
  • 线程自动利用多个CPU核心?

    假设我的应用程序运行 2 个线程 例如渲染线程和游戏更新线程 如果它在具有多核 CPU 当今典型 的移动设备上运行 我是否可以期望线程在可能的情况下自动分配给不同的核心 我知道底层操作系统内核 Android linux内核 决定调度 我的
  • JNI 不满意链接错误

    我想创建一个简单的 JNI 层 我使用Visual studio 2008创建了一个dll Win 32控制台应用程序项目类型 带有DLL作为选项 当我调用本机方法时 出现此异常 Exception occurred during even
  • 如何查找 Android 设备中的所有文件并将它们放入列表中?

    我正在寻求帮助来列出 Android 外部存储设备中的所有文件 我想查找所有文件夹 包括主文件夹的子文件夹 有办法吗 我已经做了一个基本的工作 但我仍然没有得到想要的结果 这不起作用 这是我的代码 File files array file
  • Java8无符号算术

    据广泛报道 Java 8 具有对无符号整数的库支持 然而 似乎没有文章解释如何使用它以及有多少可能 有些函数 例如 Integer CompareUnsigned 很容易找到 并且似乎可以实现人们所期望的功能 但是 我什至无法编写一个简单的
  • hibernate总是自己删除表中的所有数据

    您好 我正在开发一个 spring mvc 应用程序 它使用 hibernate 连接到存储文件的 mysql 数据库 我有两个方法 一个方法添加我选择的特定文件路径中的所有文件 另一种方法调用查询以返回从 mysql 存储的文件列表 问题
  • Microsoft Graph 身份验证 - 委派权限

    我可以使用 Microsoft Graph 访问资源无需用户即可访问 https developer microsoft com en us graph docs concepts auth v2 service 但是 此方法不允许我访问需
  • 请求位置更新参数

    这就是 requestLocationUpdates 的样子 我使用它的方式 requestLocationUpdates String provider long minTime float minDistance LocationLis
  • 如何在 JFreeChart TimeSeries 图表上显示降雨指数和温度?

    目前 我的 TimeSeries 图表每 2 秒显示一个位置的温度 现在 如果我想每2秒显示一次降雨指数和温度 我该如何实现呢 这是我的代码 import testWeatherService TestWeatherTimeLapseSer
  • 将 Long 转换为 DateTime 从 C# 日期到 Java 日期

    我一直尝试用Java读取二进制文件 而二进制文件是用C 编写的 其中一些数据包含日期时间数据 当 DateTime 数据写入文件 以二进制形式 时 它使用DateTime ToBinary on C 为了读取 DateTime 数据 它将首
  • Java中未绑定通配符泛型的用途和要点是什么?

    我不明白未绑定通配符泛型有什么用 具有上限的绑定通配符泛型 stuff for Object item stuff System out println item Since PrintStream println 可以处理所有引用类型 通
  • 如何在 Maven 中显示消息

    如何在 Maven 中显示消息 在ant中 我们确实有 echo 来显示消息 但是在maven中 我该怎么做呢 您可以使用 antrun 插件
  • Windows 上的 Nifi 命令

    在我当前的项目中 我一直在Windows操作系统上使用apache nifi 我已经提取了nifi 0 7 0 bin zip文件输入C 现在 当我跑步时 bin run nifi bat as 管理员我在命令行上看到以下消息 但无法运行
  • Android JNI C 简单追加函数

    我想制作一个简单的函数 返回两个字符串的值 基本上 java public native String getAppendedString String name c jstring Java com example hellojni He
  • 如何修复“sessionFactory”或“hibernateTemplate”是必需的问题

    我正在使用 Spring Boot JPA WEB 和 MYSQL 创建我的 Web 应用程序 它总是说 sessionFactory or hibernateTemplate是必需的 我该如何修复它 我已经尝试过的东西 删除了本地 Mav
  • java8 Collectors.toMap() 限制?

    我正在尝试使用java8Collectors toMap on a Stream of ZipEntry 这可能不是最好的想法 因为在处理过程中可能会发生异常 但我想这应该是可能的 我现在收到一个我不明白的编译错误 我猜是类型推理引擎 这是

随机推荐

  • 在vue3项目中使用新版高德地图

    高德开发平台 高德开放平台 高德地图API amap com 1 首先你要注册好账号登录 2 获取key和密钥 自2021年12月02日升级 升级之后所申请的 key 必须配备安全密钥 jscode 一起使用 NPM方式安装和使用 基础版
  • 【VS2019/C++/报错】由于找不到libmysql.dll/由于找不到libssl-1_1-x64.dll,无法继续执行代码

    错误描述 属性页的包含目录和库目录都加了 链接器的lib也加了 但是运行时报错找不到libmysql dll 把libmysql dll文件放到项目文件夹内和系统文件夹C Windows SysWOW64都没用 开始报 找不到libssl
  • android AndroidManifest的category

    CATEGORY ALTERNATIVE 设置这个activity是否可以被认为是用户正在浏览的数据的一个可选择的action CATEGORY APP BROWSER 和ACTION MAIN一起使用 用来启动浏览器应用程序 CATEGO
  • SystemviewV3.3工具使用

    SystemviewV3 3工具使用 工具下载 移植 错误解决方式 使用方法 工具下载 Systemview下载地址 移植 以不带操作系统的移植方式为例 移植源码位于 工程加入以下文件 错误解决方式 keil下多重定义问题 Objects
  • 关于TP5多语言BUG的解决办法(直接上代码)

    按照手册配置 默认语言没有反应 跳转页面之后 也没有检测当前的语言 很尴尬 最大的bug在这里 1 请用下面代码覆盖 thinkPhp library think Lang php 里面的detect 自动检测语言方法 自动侦测设置获取语言
  • jQuery 获取当前节点的html包含当前节点的方法 --$(".test").prop("outerHTML");

    在开发过程中 jQuery html 是获取当前节点下的html代码 并不包含当前节点本身的代码 然后我们有时候确需要 找遍jQuery api文档也没有任何方法可以拿到 看到有的人通过parent html 如果当前元素没有兄弟元素还行
  • Python subplots() 使用说明

    plt subplots 官方教程 link 参数 matplotlib pyplot subplots nrows 1 ncols 1 sharex False sharey False squeeze True subplot kw N
  • 雅可比矩阵和Hessian矩阵

    Jacobian矩阵和Hessian矩阵 1 Jacobian 在向量分析中 雅可比矩阵是一阶偏导数以一定方式排列成的矩阵 其行列式称为雅可比行列式 还有 在代数几何中 代数曲线的雅可比量表示雅可比簇 伴随该曲线的一个代数群 曲线可以嵌入其
  • Linux搭建测试环境详细步骤

    本文讲解如何在Linux CentOS下部署Java Web项目的步骤 环境准备 1 Linux系统 2 JDK 3 Tomcat 4 MySQL 工具下载 可从官网下载 已把安装工具存于百度网盘 链接 https pan baidu co
  • Jmeter:使用代理录制脚本

    目录 前言 介绍下各设置项 前言 当我们想要录制并回放特定应用程序或网站的交互时 使用JMeter的代理是一种常见且有效的方法 通过配置JMeter代理 它可以拦截并记录客户端与目标应用程序之间的HTTP或HTTPS通信 然后生成对应的测试
  • 如何重装Linux系统

    大家都知道我们平常所用的windows系统 可以很方便的重装系统 有U盘安装 还有光盘安装 那么我们在Linux下如何重装系统呢 其实和Windows下方法大同小异 如果你手上正好有个U盘的话 那就可以试试做一个USB Linux启动盘 它
  • javaee springMVC Map ModelMap ModelAndView el和jstl的使用

    pom依赖
  • ELK Stack 日志平台性能优化实践

    性能分析 服务器硬件Linux 1cpu4GRAM 假设每条日志250Byte 分析 logstash Linux 1cpu 4GRAM 每秒500条日志 去掉ruby每秒660条日志 去掉grok后每秒1000条数据 filebeat L
  • EduCoder_web实训作业--CSS从入门到精通——文本与字体样式

    大家注意了 由于这次好多代码都是分开的 为了方便我把每一关所有代码都发出来了 只要全选粘贴复制就可以了 第二关和第三关的答题区域都是最后一个文件夹 第一关 body 背景渐变 background webkit linear gradien
  • 50岁贷款投资的人,到底经历了什么?

    虽说 成功的投资者大多是孤独的 但是 在学习成长的过程中 参与交流讨论的好处还是很大的 一方面三人行必有我师 一方面通过他人的经验教训 深化自己的思考 前两天 看到这样一个提问 我的中国银行信用卡可以贷款2 6利率 三年还清 共贷15万 我
  • Python 模块 ddt 数据驱动测试

    简介 ddt 提供了一种方便的方法来实现数据驱动测试 Data Driven Testing 数据驱动测试是一种测试方法 通过将测试数据与测试逻辑分开 可以使用不同的数据集来运行相同的测试用例 这样可以提高测试的灵活性和可维护性 减少代码的
  • 为什么说快速排序是性能最好的排序算法?

    刚刚学习了排序这一章 看到了书中最后的一个总结表 心想从表上来看 堆排序不该是最好的排序算法么 不管最好 最坏还是平均情况 时间复杂度都是O nlogn 而且还不像快排和归并排序那样占空间 为什么说快速排序是最好的算法呢 其实经过实验 会发
  • 手把手带你利用苹果手机使用美区礼品卡升级ChatGPT Plus,轻松搞定!

    大家好 我是五竹 昨天用苹果手机尝试了一下 借助App Store 苹果应用商店 升级 Plus 成功了 一共升级了三个号 有两个一气呵成 轻松搞定 最后一个可能触发风控了 但第一时间反馈给了苹果客服 5分钟不到就解决了 不得不说别人家的客
  • 下载Visio2013镜像路径以及安装Visio2013软件

    下载Visio2013镜像路径以及安装Visio2013软件 下载镜像安装路径 https exmail qq com cgi bin ftnExs download k 5736333137c49bbd8e5ddf1c106407561d
  • Java进阶知识点5:服务端高并发的基石 - NIO与Reactor模式以及AIO与Proactor模式

    一 背景 要提升服务器的并发处理能力 通常有两大方向的思路 1 系统架构层面 比如负载均衡 多级缓存 单元化部署等等 2 单节点优化层面 比如修复代码级别的性能Bug JVM参数调优 IO优化等等 一般来说 系统架构的合理程度 决定了系统在