【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 ...
随机推荐
- libevent::事件::定时器2
#define evtimer_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg)) #include <cstdio> #include ...
- C++ Web框架::cintara
1.Cinatra是由C++开源社区purecpp发起的一个开源项目,是一个现代C++写的Web框架,旨在给用户提供一个易用.灵活和高性能的Web框架,让用户能完全专注于核心逻辑而无需关注http细节 ...
- 推荐几个IT交流社区
博客园,csdn,掘金,StackOverflow(境外),v2ex,开源中国,简书,头条
- spring boot打印sql语句-mybatis
spring boot打印sql语句-mybatis 概述 当自己编写的程序出现了BUG等等,找了很久 调试运行了几遍到mapper层也进去调试进了源码,非常麻烦 我就想打印出sql语句,好进行解决B ...
- C++ Qt基础知识
时间如流水,只能流去不流回. 学历代表你的过去,能力代表你的现在,学习能力代表你的将来. 学无止境,精益求精. 记录C++ Qt的基础知识学习记录 <C++ Qt设计模式(第二版)>
- 轻量级CNN模型mobilenet v1
mobilenet v1 论文解读 论文地址:https://arxiv.org/abs/1704.04861 核心思想就是通过depthwise conv替代普通conv. 有关depthwise ...
- 设计模式(十九)State模式
在面向对象编程中,是用类表示对象的.也就是说,程序的设计者需要考虑用类来表示什么东西.类对应的东西可能存在于真实世界中,也可能不存在于真实世界中.对于后者,可能有人看到代码后会感到吃惊:这些东西居然也 ...
- 微软宣布加入 OpenJDK,看网上各派的热闹
微软宣布加入 OpenJDK 项目(https://www.oschina.net/news/111036/microsoft-to-participate-in-openidk),这两天在微信公众号 ...
- 机器学习回顾篇(9):K-means聚类算法. slides
.caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px so ...
- 安卓—自定义 AlertDialog 的样式
自定义修改安卓弹出框的样式 效果图: 1.在style.xml下添加 <!-- 自定义弹出样式 --> <style name="MyDialogStyle" p ...