使用SpringBoot搭建web程序,里面内置了tomcat,一般都不会关心内部实现机制,上来就可以写程序,并且可以跑起来。但是是思考了每次的请求是如何工作的。

简单的来讲就是tomcat是将每次请求都将封装成一个Servlet,该Servlet来运行完业务逻辑代码,然后再有tomcat将信息返回给调用方。每个Servlet是同步的。即在该servlet的业务逻辑做完了然后才释放掉该Servlet。

但是servlet3提供了一个异步的机制,即每次请求过来之后,可以先释放该请求,但是会保存一些信息。业务逻辑由程序其他线程来处理,处理完成后将其值设置到DeferredResult里面。然后再由容器将返回值返回给前端。

这样做的好处:实现出现请求与业务IO分开,程序能够处理更多的请求。

网上可以找到其他例子来学习DeferredResult是如何运行的,即在请求内部开启一个线程来处理

@GetMapping
public DeferredResult<String> queryDevice(){
DeferredResult<String> def = new DeferredResult<>();
new Thread(()->{
//处理业务逻辑
def.setResult("处理后的结果");
}).start();
return def;
}

这样很好理解,但是不能这样做,为什么,因为每一次线程的创建销毁是消耗资源的,这样频繁的创建和销毁非常影响性能。这个时候,可以提使用线程池来处理,对是可以这样做的。是的,可以这样做,但是需要考虑到,在某一时刻,可能会产生几千个线程,这样是非常多的,如果加上tomcat创建的Servlet线程数,那确实挺消耗资源的。

上面已经有了一个可行的方案,这里提供我的一个思考,该思考是Java8新特性之后常用到的一个。

下面有三个类:

public abstract class Actor {

  public enum ActorType {
ITC, /* 立刻消费. */ BLOCKING; /* 阻塞.*/
}
/**
* actor类型.
*/
public ActorType type;
/**
* actor名.
*/
public String name;
public Actor(ActorType type) {
this.type = type;
this.name = this.getClass().getSimpleName();
}
/**
* 任务消费
*/
public void future(Consumer<Void> c) {
if (this.type.ordinal() == ActorType.BLOCKING.ordinal()) {//阻塞
((ActorBlocking) this).push(c);
return;
} else {
Misc.exeConsumer(c, null);
}
}
}
public class ActorBlocking extends Actor {

  /**
* 等待处理的Consumer.
*/
private ConcurrentLinkedQueue<Consumer<Void>> cs = new ConcurrentLinkedQueue<>();
/**
* 拥有线程的个数
*/
private int tc = 1; /**
* cs的size
*/
private AtomicInteger size = new AtomicInteger(0); /**
* 线程忙?
*/
public volatile boolean busy = false; public ActorBlocking() {
super(ActorType.BLOCKING);
} /**
* 添加任务.
*/
public void push(Consumer<Void> c) {
this.cs.add(c);
this.size.incrementAndGet();
synchronized (this) {//通知线程消费信息
this.notify();
}
} /**
* 线程忙?
*/
public boolean isBusy() {
return this.busy;
} /**
* 队列尺寸.
*/
public int size() {
return this.size.get();
} public int getTc() {
return tc;
} public void setTc(int tc) {
this.tc = tc < 1 ? 1 : tc;
} /**
* 启动线程
*/
protected void start() {
ActorBlocking ab = this;
ExecutorService ex = Executors.newFixedThreadPool(this.tc);//创建线程池
for (int i = 0; i < tc; i++) {
ex.execute(() -> {
while (true) {
ab.run();
}
});
}
} /**
* 抢占式消费任务
*/
private void run() {
Consumer<Void> c = this.cs.poll();
if (c == null) {
synchronized (this) {
try {
this.wait();
} catch (InterruptedException e) {
}
}
c = this.cs.poll();
}
if (c != null) /* 抢占式. */ {
this.size.decrementAndGet();
this.busy = true;
Misc.exeConsumer(c, null);
this.busy = false;
}
}
}
@Component
public class AppActorBlocking extends ActorBlocking {
//可以设置CPU*2的
private int threadSize = 4; public AppActorBlocking() {
this.setTc(threadSize);//设置线程数量
this.start();
}
}

该方法是工具Misc类总的方法:

 /**
* 执行Consumer并将异常化解在内部.
*/
public static final <T> boolean exeConsumer(Consumer<T> c, T t) {
try {
c.accept(t);
return true;
} catch (Exception e) {
if (logger.isWarnEnabled()) {
logger.warn("{}", Misc.trace(new Throwable()));
}
if (logger.isWarnEnabled()) {
logger.warn("t: {}, e: {}", t, Misc.trace(e));
}
return false;
}
}

如何调用:

@Autowired
public AppActorBlocking appBlocking; public void method(){
appBlocking.future(v->{
//处理逻辑代码
});
}

上面的代码理解是所有的业务逻辑都是一个个Task,每一次请求过来,那么我就将业务逻辑代码生成一个Task,放入到队列中,然后由线程去取其中的任务来消费。

这里仅仅是换了一个思路,不是由线程池去创建线程来处理,而是创建几个线程,然后抢占式的去消费任务,而过来的每次请求,都会放入到队列中。

DeferredResult的异步处理能够提升一些服务器的性能,处理更多的连接数,但是一个WEB程序,处理连接数还与内置默认的tomcat相关(SpringBoot下还有其他容器),即tomcat默认的处理最大连接数为200,除了最大连接数,还有一个tomcat的最大处理线程数,如果该处设置小了,那么并发也一定会小,在设置这些之外,需要设置一个等待队列的大小,总有一些请求是不能被处理的,但又不能拒绝掉,否则用户体验特别不好,那么就进入到等待队列中,等tomcat有空闲的线程再来处理等待队列中的线程。

至于什么时候用到该DeferredResult,如果是访问量不大的程序,如管理系统,没必要使用到这个,毕竟没有访问量,反而增大了开发量,但是如果做了很好的封装,那么就没关系了,这个就考量各自程序员的水平了。

关于DeferredResult的思考的更多相关文章

  1. 领域驱动和MVVM应用于UWP开发的一些思考

    领域驱动和MVVM应用于UWP开发的一些思考 0x00 起因 有段时间没写博客了,其实最近本来是根据梳理的MSDN上的资料(UWP开发目录整理)有条不紊的进行UWP学习的.学习中有了心得体会或遇到了问 ...

  2. 关于面试题 Array.indexof() 方法的实现及思考

    这是我在面试大公司时碰到的一个笔试题,当时自己云里雾里的胡写了一番,回头也曾思考过,最终没实现也就不了了之了. 昨天看到有网友说面试中也碰到过这个问题,我就重新思考了这个问题的实现方法. 对于想进大公 ...

  3. 关于 CSS 反射倒影的研究思考

    原文地址:https://css-tricks.com/state-css-reflections 译者:nzbin 友情提示:由于演示 demo 的兼容性,推荐火狐浏览.该文章篇幅较长,内容庞杂,有 ...

  4. 关于.NET参数传递方式的思考

    年关将近,整个人已经没有了工作和写作的激情,估计这个时候很多人跟我差不多,该相亲的相亲,该聚会喝酒的聚会喝酒,总之就是没有了干活的心思(我有很多想法,但就是叫不动我的手脚,所以我只能看着别人在做我想做 ...

  5. 使用NUnit为游戏项目编写高质量单元测试的思考

    0x00 单元测试Pro & Con 最近尝试在我参与的游戏项目中引入TDD(测试驱动开发)的开发模式,因此单元测试便变得十分必要.这篇博客就来聊一聊这段时间的感悟和想法.由于游戏开发和传统软 ...

  6. OpenGL shader 中关于顶点坐标值的思考

    今天工作中需要做一个事情: 在shader内部做一些空间距离上的计算,而且需要对所有的点进行计算,符合条件的显示,不符合条件的点不显示. 思路很简单,在vertex shader内知道顶点坐标,进行计 ...

  7. 关于领域驱动设计(DDD)中聚合设计的一些思考

    关于DDD的理论知识总结,可参考这篇文章. DDD社区官网上一篇关于聚合设计的几个原则的简单讨论: 文章地址:http://dddcommunity.org/library/vernon_2011/, ...

  8. 关于bug分析与异常处理的一些思考

    前言:工作三年了,工作内容主要是嵌入式软件开发和维护,用的语言是C,毕业后先在一家工业自动化控制公司工作两年半,目前在一家医疗仪器公司担任嵌入式软件开发工作.软件开发中,难免不产生bug:产品交付客户 ...

  9. 【数据库】_由2000W多条开房数据引发的思考、实践----给在校生的一个真实【练耙场】,同学们,来开始一次伟大的尝试吧。

      ×   缘起---闲逛博客园 前几天的时候,在某一QQ群看到一条消息“XXX酒店开房XXXBTXX迅雷BT下载”,当时是一目十行的心态浏览,目光掠过时, 第一反应我想多了~以为是XX种子(你懂的~ ...

随机推荐

  1. set集合排序

    不仅list是有序集合,set也可以变为有序集合. /** * 给字符串时间的set排序 * @return 有序的set集合 */ public static Set getSort(){ Set& ...

  2. Java如何实现按指定行读取文件

    最近在开发实战中,遇到了一个这样的技术情景: 把log4j生成的日志文件定时刷进MySQL数据库,比如三个小时刷一次,那么每次刷数据的时候,如何控制文件读取是从上一次文件读取结束的地方开始继续读取的? ...

  3. Nhibernate 存储过程获取返回值

    写在前面:因为项目使用ssh.net所以做着做着要调用存储过程,而且是有返回值的,按照以前的做法直接在参数里指定下就可以获取,但是在nhibernate里调用就有点陌生了,百度一下得出的结果有两种:第 ...

  4. Java50道经典习题-程序2 输出素数

    题目:判断101-200之间有多少个素数,并输出所有素数 分析:判断素数的方法:用一个数分别去除2到(这个数-1)的数,如果能被整除,则表明此数不是素数,反之是素数. public class Pro ...

  5. maven-multiModule

    主pom的定义 packaging:pom modules的指定 dependencyManagement的指定 properties的指定 build或profile的设置 子module的创建 在 ...

  6. Visual Studio Error

    Visual Studio Error 注意:文中所有“系统”用词,均指Windows Console操作系统IO Debug Error 错误类型 #0表示调用约定错误 可以考虑在指针前面加上_st ...

  7. Transaction And Lock--事务中使用return会回滚事务吗?

    事务中使用return会回滚事务吗? 答案:不会,如果在事务中没有显示提交或回滚事务边return,事务不会被提交或回滚,在C#中,如果没有使用连接池,则事务在连接断开和销毁时被强制回滚,如果使用连接 ...

  8. 菜鸟的Xamarin.Forms前行之路——实现按钮的字体图标(可扩展)

    在实际的APP中,带有图标的按钮用到地方还是蛮多的,字体图标往往能更快更生动的传达信息,并且相对于背景图片,字体图标也有着绝对的优势,所以实现按钮的字体图标是值得尝试的. 实现方法:各平台自定义渲染按 ...

  9. Cntlm

    SourceForge页面 作用是转发请求到代理 可以同时加上域验证信息(为各种NTLM认证?) gradle.cmd.Terminal等工具或软件可能无法或者说没有使用系统/浏览器设置的代理,因为可 ...

  10. 食物(矩阵快速幂)(DP)

    这个题..我们可以想到用递推写!!qwq(好吧,其实我的DP水平不高啊qwq) 就是我们以两个为单位(一共九种组合情况),然后往后面推下一位的情况. 通过手动模拟,我们可以找到它们之间的递推关系(详见 ...