【SSM Spring 线程池 OJ】 使用Spring线程池ThreadPoolTaskExecutor
【经验分享,非教程】
最近做的Online Judge项目,在本地判题的实现过程中,遇到了一些问题,包括多线程,http通信等等。现在完整记录如下:
OJ有一个业务是:
用户在前端敲好代码,按下提交按钮发送一个判题请求给后端,后端收到这个请求后,将具体的内容再转交给一个独立的评测机服务,等待评测机给出判题的结果,再写入数据库完成一次完整的判题。
一开始,我的具体实现的思路是这样的:
首先简单介绍一下我用的评测机,是一个大佬学长写的,具体的实现和现在主流的评测机轮询数据库拿出待判题内容去判题不同,他是一个独立存在的服务。我们的后端通过Http访问到评测机的通讯模块,提交对应的代码,评测机通过docker模拟一个评测环境,跑一遍代码,比对输入输出文件,然后得到结果记录在磁盘上。同样,通过Http访问评测机得到评测的结果。(具体的实现会在后续其他博文中提到)
介绍完评测机的具体功能,然后我们就要去实现了。我先从Controller中得到用户提交请求中的代码等信息,先把这些记录存入数据库的对应表中,然后将代码等内容通过http协议发送给评测机,接着等待2s,从评测机中拿到结果。经过测试,可以实现功能。
测试功能是正确可用的,但是问题也随之而来。由于Controller执行完会返回给用户一段json消息,而等待2s是写在Contoller中的,是Contoller这个主进程的,所以执行完代码永远需要2s才能反馈给前端。这样子,用户体验差不说,逻辑也有很大的缺陷。单个用户提交可能2s就能拿到结果,如果多个提交评测机评测速度较慢,可能3s才能得到结果,后端没办法得到正确的评测机的反馈,不就全部乱套了吗?
自然而然,我就想使用多线程来解决这个问题。那么好,开始动手,由于之前接触多线程较少,所以我直接定义了一个Runnable执行一个2S读取一下评测机的方法,在Controller的线程上再开一个线程进行执行。类似这样:
new Thread(new Runnable() {
@Override
public void run() {
//这里执行两秒钟从评测机获取一次评测结果的方法
dosomething();
}
}).start();
测试是没问题的,但是新的问题出现了,我们知道Controller在spring中是单例模式存在,多线程调用的,每个用户调用它的线程是独立的,同理,一旦在Controller中执行到这个函数,一个新的线程就将开启,而不会在执行完毕后主动停止(当然也可以手动写入代码停止,但是不方便嘛),而且原生的开线程的方式是比较耗时的,那为什么不用线程池来管理呢。
为了达成进一步的修改,我在Controller中使用了如下的语句,定义了一个缓存线程池,这个线程池具体的可以参考:https://www.cnblogs.com/zhujiabin/p/5404771.html:
ExecutorService service = Executors.newCachedThreadPool();
然后在具体的实现中,使用如下语句将业务逻辑加入线程中执行:
service.execute(new Runnable() {
@Override
public void run() {//这里执行两秒钟从评测机获取一次评测结果的方法
dosomething();
}
});
经过我人工测试多次提交,完全没有卡顿,难道这就好了吗?问题没那么简单...为了知道评测机的抗压能力,我用了Postman来模拟并发,由于缓存线程池会在线程有空闲的时候使用空闲线程,没有空闲的时候开新的线程,于是我一次性发送了2000条请求给后端,于是我的8核小霸王就差点宕机了,16GB内存用了13GB,仔细一看一个tomcat容器用了3GB内存,问题就这么来了,我启动了线程池,但是没有去关闭它,哪怕我停止了这个web应用,这个线程池内的线程属于非守护进程,不会被tomcat容器干掉,所以依旧在那。
====================================================================以下是解决方案=====================================================================
为了解决这个问题,我翻了好几页百度(并没有),想通过监听web应用的开启关闭来手动关闭线程池,但是却发现了spring也实现了一个线程池类org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor,现在问题好解决了,使用spring的线程池更好配置。具体可参考:https://www.cnblogs.com/jpfss/p/9754024.html
我们可以很方便通过xml配置线程池的具体参数,作为bean存在的线程池也会在tomcat关闭后被关闭(根据常识判断,但是这点存疑,新开的线程是否会跟着web应用关闭掉,暂未测试完整),岂不美哉,下面给出配置:
在applicationContext.xml中配置bean
<!--Spring线程池-->
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<!-- 核心线程数 -->
<property name="corePoolSize" value="5" />
<!-- 线程池维护线程的最大数量 -->
<property name="maxPoolSize" value="100" />
<!-- 允许的空闲时间, 默认60秒 -->
<property name="keepAliveSeconds" value="60" />
<!-- 缓存队列长度 -->
<property name="queueCapacity" value="50" />
<!-- 线程超过空闲时间限制,均会退出直到线程数量为0 -->
<property name="allowCoreThreadTimeOut" value="true"/>
<!-- 对拒绝task的处理策略 -->
<property name="rejectedExecutionHandler">
<bean class="java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy" />
</property>
</bean>
在Controller中装载上
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; /**
* @author axiang
*/
@RequestMapping("/submit")
@Controller
public class SomeController { @Autowired
private ThreadPoolTaskExecutor executor; @RequestMapping("/dosomethingMethod")
public JsonInfo dosomethingMethod(HttpServletRequest req) {
//do something
executor.execute(new Runnable() {
@Override
public void run() {
dosomething();
}
});
return new JsonInfo();
}
}
大功告成!现在线程池交给spring维护去了,只管可劲开线程就是了:)一旦线程闲置超过配置的时长,spring就会把整个线程池回收
当然,还是存在问题的,这个线程池的实现原理还没弄懂,并且在线程中还对数据库进行了操作,暂不知道这种做法会不会对数据库的插入造成什么奇怪的影响,等待测试。
【SSM Spring 线程池 OJ】 使用Spring线程池ThreadPoolTaskExecutor的更多相关文章
- java 线程池(ExecutorService与Spring配置threadPoolTaskExecutor)
一.java ExecutorService实现 创建ExecutorService变量private ExecutorService executor = null 2.执行对应任务时,首先生成线程 ...
- Tomcat启动时加载数据到缓存---web.xml中listener加载顺序(例如顺序:1、初始化spring容器,2、初始化线程池,3、加载业务代码,将数据库中数据加载到内存中)
最近公司要做功能迁移,原来的后台使用的Netty,现在要迁移到在uap上,也就是说所有后台的代码不能通过netty写的加载顺序加载了. 问题就来了,怎样让迁移到tomcat的代码按照原来的加载顺序进行 ...
- spring boot:使用多个线程池实现实现任务的线程池隔离(spring boot 2.3.2)
一,为什么要使用多个线程池? 使用多个线程池,把相同的任务放到同一个线程池中,可以起到隔离的作用,避免有线程出错时影响到其他线程池,例如只有一个线程池时,有两种任务,下单,处理图片,如果线程池被处理图 ...
- 线程中无法实例化spring注入的服务的解决办法
问题描述 在Java Web应用中采用多线程处理数据,发现Spring注入的服务一直报NullPointerException.使用注解式的声明@Resource和XML配置的bean声明,都报空指针 ...
- 【转】Spring 获取web根目录 (Spring线程获取web目录/路径/根目录,普通类获取web目录)
不使用Spring,怎样能在Listener启动的Thread中获取web目录,还真不完全确定.其实我觉得实际代码也很简单.就是基于普通的listener,然后在listener中获取web目录并放到 ...
- spring配置datasource三种方式 数据库连接池
尊重原创(原文链接):http://blog.csdn.net/kunkun378263/article/details/8506355 1.使用org.springframework.jdbc.da ...
- SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)
1.前言 使用框架都是较新的版本: Spring 4.0.2 RELEASE Spring MVC 4.0.2 RELEASE MyBatis 3.2.6 2.Maven引入需要的JAR包 2.1设置 ...
- [转]SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)
原文地址:http://blog.csdn.net/zhshulin/article/details/37956105#comments 使用SSM(Spring.SpringMVC和Mybatis) ...
- SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)【转载】
最近在学习Spring+SpringMVC+MyBatis的整合.以下是参考网上的资料自己实践操作的详细步骤. 1.基本概念 1.1.Spring Spring是一个开源框架,Spring是于20 ...
随机推荐
- enable_shared_from_this
头文件<memory> enable_shared_from_this是一个模板类. 使用场景:需要把自己类对象作为参数传给其他函数时,就需要传递一个指向自身的share_ptr. str ...
- (22)ASP.NET Core EF创建模型(索引、备用键、继承、支持字段)
1.索引 索引是跨多个数据存储区的常见概念.尽管它们在数据存储中的实现可能会有所不同,但也可用于基于列(或一组列)更高效地进行查找. 1.1约定 按照约定,将在用作外键的每个属性(或一组属性)中创建索 ...
- VMware安装和linux(centos7)系统安装
下载centos系统ISO镜像 安装linux系统和winsdows安装系统一样,需要系统文件.浏览器访问centos官网进行下载,http://www.centos.org,因为是国外网站所有下载速 ...
- 百万年薪python之路 -- 生成器
1.生成器 #本质就是迭代器 1.1 生成器的构建方式 在python中有三种方式来创建生成器: 1.通过生成器函数 2.通过生成器推导式 3.python内置函数或者模块提供 1.2 生 ...
- C# 表达式树 Expression
表达式树是定义代码的数据结构. 它们基于编译器用于分析代码和生成已编译输出的相同结构. 几种常见的表达式 BinaryExpression 包含二元运算符的表达式 BinaryExpression b ...
- lamda和匿名内部类
匿名内部类 匿名内部类在日常编程中还是经常会使用的.比如 ArrayList<String> list=new ArrayList<>(); list.add(new Stri ...
- 微服务SpringCloud之GateWay路由
在前面博客学习了网关zuul,今天学下spring官方自带的网关spring cloud gateway.Zuul(1.x) 基于 Servlet,使用阻塞 API,它不支持任何长连接,如 WebSo ...
- java script三大组成部分
JavaScript是一种属于网络的脚本语言,已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果.通常JavaScript脚本是通过嵌入在HTML中来实现 ...
- ApplicationContextAware使用理解
接口的作用 当一个类实现了这个接口(ApplicationContextAware)之后,Aware接口的Bean在被初始之后,可以取得一些相对应的资源,这个类可以直接获取spring 配置文件中 所 ...
- 第二篇 Flask的Response三剑客及两个小儿子
一.Response三剑客 (一)Flask中的HTTPResponse @app.route("/") #app中的route装饰器 def index(): #视图函数 ret ...