@Slf4j
@Component
public class CustomThreadPoolManager {
// 线程池维护线程的最少数量
private final static int CORE_POOL_SIZE = 2;
// 线程池维护线程的最大数量
private final static int MAX_POOL_SIZE = 10;
// 线程池维护线程所允许的空闲时间
private final static int KEEP_ALIVE_TIME = 0;
// 线程池所使用的缓冲队列大小
private final static int WORK_QUEUE_SIZE = 50;
/**
* 用于储存在队列中的试卷,防止重复提交,在真实场景中,可用redis代替 验证重复
*/
Map<String, Object> cacheMap = new ConcurrentHashMap<>();
/**
* 试卷的缓冲队列,当线程池满了,则将试卷存入到此缓冲队列
*/
Queue<Object> msgQueue = new LinkedBlockingQueue<Object>();
/**
* 当线程池的容量满了,执行下面代码,将试卷存入到缓冲队列
*/
final RejectedExecutionHandler handler = new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
//试卷加入到缓冲队列
msgQueue.offer(((ExamPaperThread) r).getPaperKey());
log.info("系统任务太忙了,把此试卷交给(调度线程池)逐一处理,试卷号:"
+ ((ExamPaperThread) r).getPaperKey());
}
};
/**创建线程池*/
final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS,
new ArrayBlockingQueue(WORK_QUEUE_SIZE), this.handler);
/**将任务加入试卷线程池*/
public void addExamPaper(String paperKey, List<ExamPaperResponsePo> po){
log.info("此试卷准备添加到线程池,试卷号:" + paperKey);
//通过试卷ID验证当前进入的试卷是否已经存在
if (cacheMap.get(paperKey) == null) {
log.info("此试卷已添加到线程池,试卷号:" + paperKey);
cacheMap.put(paperKey, po);
ExamPaperThread examPaperThread = new ExamPaperThread(paperKey, po);
threadPool.execute(examPaperThread);
}
}
/**
* 线程池的定时任务----> 称为(调度线程池)。此线程池支持 定时以及周期性执行任务的需求。
*/
final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5);
/**
* 检查(调度线程池),每秒执行一次,查看试卷的缓冲队列是否有 试卷记录,则重新加入到线程池
*/
final ScheduledFuture scheduledFuture = scheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
//判断缓冲队列是否存在记录
if(!msgQueue.isEmpty()){
//当线程池的队列容量少于WORK_QUEUE_SIZE,则开始把缓冲队列的试卷 加入到 线程池
if (threadPool.getQueue().size() < WORK_QUEUE_SIZE) {
String paperKey = (String) msgQueue.poll();
List<ExamPaperResponsePo> po = null;
if(null != cacheMap.get(paperKey)){
po = (List<ExamPaperResponsePo>)cacheMap.get(paperKey);
}
ExamPaperThread examPaperThread = new ExamPaperThread(paperKey,po);
threadPool.execute(examPaperThread);
log.info("(调度线程池)缓冲队列出现试卷业务,重新添加到线程池,试卷号:"+paperKey);
}
}
}
}, 0, 1, TimeUnit.SECONDS);
/**获取消息缓冲队列*/
public Queue<Object> getMsgQueue() {
return msgQueue;
}
/**终止试卷线程池+调度线程池*/
public void shutdown() {
//true表示如果定时任务在执行,立即中止,false则等待任务结束后再停止
log.info("终止试卷线程池+调度线程池:"+scheduledFuture.cancel(false));
scheduler.shutdown();
threadPool.shutdown();
}
}
@Slf4j
@Component
@Scope("prototype")//spring多例 单例会有线程安全问题 //我自己其实觉得好像单利多例并不影响什么。。。
public class ExamPaperThread implements Runnable {
private RedisService redisService;
private ObjectMapper mapper;
//试卷ID
private String paperKey;
//试卷明细
private List<ExamPaperResponsePo> examPaperResponsePoList;
public List<ExamPaperResponsePo> getExamPaperResponsePoList() {
return examPaperResponsePoList;
}
public void setExamPaperResponsePoList(List<ExamPaperResponsePo> examPaperResponsePoList) {
this.examPaperResponsePoList = examPaperResponsePoList;
}
public String getPaperKey() {
return paperKey;
}
public void setPaperKey(String paperKey) {
this.paperKey = paperKey;
}
public ExamPaperThread() {
}
public ExamPaperThread(String paperKey, List<ExamPaperResponsePo> examPaperResponsePoList) {
this.paperKey = paperKey;
this.examPaperResponsePoList = examPaperResponsePoList;
}
@Override
public void run() {
log.info("线程启动:"+paperKey);
for(ExamPaperResponsePo examPaperResponsePo : examPaperResponsePoList){
try {
this.redisService = SpringContextUtil.getBean(RedisService.class);
this.mapper = SpringContextUtil.getBean(ObjectMapper.class);
String paperId = examPaperResponsePo.getPaperId();
int paperIndex = examPaperResponsePo.getPaperIndex();
String key = RdpConstant.EXAM_CACHE_PREFIX + paperId + "_" + paperIndex;
redisService.del(key);
redisService.set(key, mapper.writeValueAsString(examPaperResponsePo));
log.info("SaveExamPaper successful! key : " + key);
} catch (Exception e) {
log.error("SaveExamPaper failed! ", e);
}
}
}
}