【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 ...
随机推荐
- php数组怎么根据其中的一个值进行排序?解决方法
有一个数组如下 我想对其进行一个排序,按照其“sort”的值的大小进行排序,3->4->7. 解决方法如下: 1.首先介绍php的两个方法 方法一:array_column() 返回输入数 ...
- Hello World ! 节日快乐!
节日快乐! 世界你好,Hello World Java public class HelloWorld{ public static void main(String[] args) { System ...
- opencv::像素重映射
像素重映射(cv::remap) 简单点说就是把输入图像中各个像素按照一定的规则映射到另外一张图像的对应位置上去,形成一张新的图像. Remap( InputArray src, // 输入图像 Ou ...
- 10.Linux用户权限
1.权限基本概述 1. 什么是权限? 我们可以把它理解为操作系统对用户能够执行的功能所设立的限制,主要用于约束用户能对系统所做的操作,以及内容访问的范围,或者说,权限是指某个特定的用户具有特定的系统资 ...
- jenkins pipeline 流水线生产
jenkins pipeline : pipeline { agent any parameters { string(name: 'git_version', defaultValue: 'v1.1 ...
- springboot使用spring配置文件
1.如何在springboot中使用spring的配置文件,使用@Configuration和@ImportResource注解 package com.spring.task; import org ...
- OptimalSolution(2)--二叉树问题(3)Path路径问题
一.在二叉树中找到累加和为指定值的最长路径长度 给定一棵二叉树和一个32位整数sum,求累加和为sum的最长路径长度.路径是指从某个节点往下,每次最多选择一个孩子节点或者不选所形成的节点链 -3 / ...
- Java基础(三)对象与类
1.类的概念:类是构造对象的模板或蓝图.由类构造对象的过程称为创建类的实例. 2.封装的概念:封装(有时称为数据隐藏)是与对象有关的一个重要概念.对象中的数据称为实例域,操纵数据的过程称为方法.对于每 ...
- 第三篇 Flask中的request
每个框架中都有处理请求的机制(request),但是每个框架的处理方式和机制是不同的,下面我们来了解一下Flask的request中都有什么东西 from flask import request 1 ...
- [无用]LNC李纳川的日常NC操作
NC说他从不CE NC说他想明白了 表示嘲讽. 好吧好吧其实还是有一个美好的结局的. 虽说我在嘲讽他,但我并不会做TAT 大神吹牛没毛病,我个蒟蒻还是老老实实刷水题吧.