【经验分享,非教程】

最近做的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的更多相关文章

  1. java 线程池(ExecutorService与Spring配置threadPoolTaskExecutor)

    一.java ExecutorService实现 创建ExecutorService变量private ExecutorService executor = null 2.执行对应任务时,首先生成线程 ...

  2. Tomcat启动时加载数据到缓存---web.xml中listener加载顺序(例如顺序:1、初始化spring容器,2、初始化线程池,3、加载业务代码,将数据库中数据加载到内存中)

    最近公司要做功能迁移,原来的后台使用的Netty,现在要迁移到在uap上,也就是说所有后台的代码不能通过netty写的加载顺序加载了. 问题就来了,怎样让迁移到tomcat的代码按照原来的加载顺序进行 ...

  3. spring boot:使用多个线程池实现实现任务的线程池隔离(spring boot 2.3.2)

    一,为什么要使用多个线程池? 使用多个线程池,把相同的任务放到同一个线程池中,可以起到隔离的作用,避免有线程出错时影响到其他线程池,例如只有一个线程池时,有两种任务,下单,处理图片,如果线程池被处理图 ...

  4. 线程中无法实例化spring注入的服务的解决办法

    问题描述 在Java Web应用中采用多线程处理数据,发现Spring注入的服务一直报NullPointerException.使用注解式的声明@Resource和XML配置的bean声明,都报空指针 ...

  5. 【转】Spring 获取web根目录 (Spring线程获取web目录/路径/根目录,普通类获取web目录)

    不使用Spring,怎样能在Listener启动的Thread中获取web目录,还真不完全确定.其实我觉得实际代码也很简单.就是基于普通的listener,然后在listener中获取web目录并放到 ...

  6. spring配置datasource三种方式 数据库连接池

    尊重原创(原文链接):http://blog.csdn.net/kunkun378263/article/details/8506355 1.使用org.springframework.jdbc.da ...

  7. 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设置 ...

  8. [转]SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)

    原文地址:http://blog.csdn.net/zhshulin/article/details/37956105#comments 使用SSM(Spring.SpringMVC和Mybatis) ...

  9. SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)【转载】

    最近在学习Spring+SpringMVC+MyBatis的整合.以下是参考网上的资料自己实践操作的详细步骤. 1.基本概念   1.1.Spring Spring是一个开源框架,Spring是于20 ...

随机推荐

  1. Halcon一日一练:图像分割之基本概念

    1.什么是图像分割: 图像分割就是把图像中特定的目标提出来,进行处理. 2.为什么要做图像分割: 图像分割是由图像处理到图像分析的关键步骤,准确的来说,没有图像分割,图像处理将无法实现其后续的操作.进 ...

  2. 从Go语言编码角度解释实现简易区块链——实现交易

    在公链基础上实现区块链交易 区块链的目的,是能够安全可靠的存储交易,比如我们常见的比特币的交易,这里我们会以比特币为例实现区块链上的通用交易.上一节用简单的数据结构完成了区块链的公链,本节在此基础上对 ...

  3. scp -本地文件上传服务器,指定端口

    scp 命令可以将本地文件上传服务器,或者将服务器上的文件下载到本地, 1.  上传服务器: scp [本地文件目录]  [服务器用户名]@[服务器名]:/[服务器上文件路径] 比如 scp /Doc ...

  4. vue 开发插件流程

    UI demo UI 插件汇总 我的github iSAM2016 在练习写UI组件的,用到全局的插件,网上看了些资料.看到些的挺好的,我也顺便总结一下写插件的流程: 声明插件-> 写插件-&g ...

  5. OptimalSolution(2)--二叉树问题(1)遍历与查找问题

    一.二叉树的按层打印与ZigZag打印 1.按层打印: 1 Level 1 : 1 / \ 2 3 Level 2 : 2 3 / / \ 4 5 6 Level 3 : 4 5 6 / \ 7 8 ...

  6. TCP Socket服务端客户端(二)

    本文服务端客户端封装代码转自https://blog.csdn.net/zhujunxxxxx/article/details/44258719,并作了简单的修改. 1)服务端 此类主要处理服务端相关 ...

  7. 针对工程实践项目的用例建模Use Case Modeling

    一.什么是用例建模(Use Case Modeling) 1.用例(Use Case) (1)概念:用例是软件工程或系统工程中对系统如何反应外界请求的描述,是一种通过用户的使用场景来获取需求的技术. ...

  8. UE4蓝图与C++交互——射击游戏中多武器系统的实现

    回顾   学习UE4已有近2周的时间,跟着数天学院"UE4游戏开发"课程的学习,已经完成了UE4蓝图方面比较基础性的学习.通过UE4蓝图的开发,我实现了类似CS的单人版射击游戏,效 ...

  9. 使用 EW 作Socks5代理

    简介: EarthWorm是一款用于开启 SOCKS v5 代理服务的工具,基于标准 C 开发,可提供多平台间的转接通讯,用于复杂网络环境下的数据转发. 主页: http://rootkiter.co ...

  10. 强大的CompletableFuture

    引子 为了让程序更加高效,让CPU最大效率的工作,我们会采用异步编程.首先想到的是开启一个新的线程去做某项工作.再进一步,为了让新线程可以返回一个值,告诉主线程事情做完了,于是乎Future粉墨登场. ...