前言
在工作中的一个场景中,需要紧急处理五千多份合同,合同处理的过程有些复杂,这里说的复杂不是代码复杂,是中间需要经过多个服务渠道,包括对外对接的一些业务,所以这五千来分如果同步处理的话,估计要跑上估计至少也得半天的时间了,而后期确定了还会面临同样的问题(坑爹的代码与凌乱的架构问题),因此写了一个处理合同的线程池,越写兴趣越浓,最后写出来以后发现这个鸟玩意儿实在是太通用了,几乎可以用在所有场景下的批量任务。
简述
这个线程池可以说是为批量任务量身定做的一套方案,并且几乎可以实现任何场景下的批量任务。大体分为4个部分:
- 约束者
- 执行者
- 管理者
- 发起者
约束者
约束者主要是对整个线程执行类的一个约束,他定义了公共的一致的接口,方便其他角色调度,我给他起名为ThreadTask的一个借口类,以下是代码,其中T表示要处理的数据类型:
package com.cnpany.common.util.thread;
import com.cnpany.common.util.thread.exception.ThreadTaskIncompleteExecutionException;
/**
* 多线程任务
* @author 郭胜凯
* @time 2017年10月28日 上午11:28:23
* @email guoshengkai@shoujinwang.com
*/
public interface ThreadTask<T> {
/**
* 开始线程
*/
void start();
/**
* 结束线程
*/
void stop();
/**
* 获得任务数
* @return
*/
int getTaskCount();
/**
* 获得线程数
* @return
*/
int getThreadCount();
/**
* 等待完成
* @return
* 等待时间
*/
long doWait();
/**
* 等待完成
* @param wautTime
* 最长等待时间
* @return
* 实际等待时间
*/
long doWait(long wautTime) throws ThreadTaskIncompleteExecutionException;
}
##执行者
执行者类似一个员工的角色,他负责执行整个批量任务的实现,我更喜欢叫他'机器人'
所以,他也是一个接口类,这个类只有一个excute方法,下面是这个机器人
的具约束码,其中T表示要处理的数据类型,K表示在处理过程中需要用到的对象。因为我需要对数据库以及其他的一些远程业务进行操作,所以我需要把Service交给这个机器人,让机器人用这个Service去做一些事情。
package com.cnpany.common.util.thread;
/**
* 线程工作者
* @author 郭胜凯
* @time 2017年10月28日 上午11:25:05
* @email guoshengkai@shoujinwang.com
*/
public interface ThreadRobot<T, K> {
/**
* 工作函数
* @param t
*/
void excute(T t, K param);
}
##管理者
这里的管理者,是针对Thread Task这个接口的实现,我给他起名为BatchThreadTask,也就是说,这个管理者承担一个批处理任务的管理工作,当然,根据不同的业务需求,也可以创建更多的管理者,不签这个批处理的角色几乎可以实现绝大多数业务的批量操作了,下面是具体代码:
package com.cnpany.common.util.thread.impl;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.locks.Lock;
import com.cnpany.common.util.thread.ThreadRobot;
import com.cnpany.common.util.thread.ThreadTask;
import com.cnpany.common.util.thread.exception.NotStartedException;
import com.cnpany.common.util.thread.exception.NullTaskException;
import com.cnpany.common.util.thread.exception.ThreadTaskIncompleteExecutionException;
/**
* 批量线程任务
* @author 郭胜凯
* @time 2017年10月28日 上午11:33:05
* @email guoshengkai@shoujinwang.com
*/
public class BatchThread<T, K> implements ThreadTask<T> {
/**
* 消费内容
*/
private LinkedList<T> param = new LinkedList<>();
/**
* 最大线程数
*/
private int maxThread = 3;
/**
* 当前线程数
*/
private int thisThread = 0;
/**
* 执行机器人
*/
private ThreadRobot<T, K> roboot;
private long startTime = 0;
private K member = null;
private Lock lock = null;
private boolean over = false;
private boolean exit = false;
/**
* 创建批量任务
* @param roboot
* 任务执行机器人
* @param maxThread
* 最大机器人数量
* @param param
* 消费内容
*/
public BatchThread(ThreadRobot<T, K> roboot, int maxThread, List<T> param, Lock lock, K member) {
if(null == param || param.isEmpty()) {
throw new NullTaskException();
}
if(maxThread > 0) {
this.maxThread = maxThread;
}
for (T t : param) {
this.param.addLast(t);
}
this.roboot = roboot;
this.lock = lock;
this.member = member;
}
@Override
public void start() {
startTime = System.currentTimeMillis();
for(int i = 1; i <= maxThread; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
T item = null;
while((item = getList()) != null) {
if(exit) {
break;
}
roboot.excute(item, member);
}
}finally {
synchronized (lock) {
thisThread --;
if(thisThread == 0) {
over = true;
}
}
}
}
});
t.start();
thisThread ++;
}
}
@Override
public void stop() {
//不实现
}
@Override
public int getTaskCount() {
return param.size();
}
@Override
public int getThreadCount() {
// TODO Auto-generated method stub
return thisThread;
}
@Override
public long doWait() {
while(!over) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
continue;
}
if(startTime == 0) {
throw new NotStartedException();
}
return System.currentTimeMillis() - startTime;
}
private T getList() {
synchronized (lock) {
try {
return param.removeFirst();
} catch (NoSuchElementException e) {
return null;
}
}
}
@Override
public long doWait(long wautTime) throws ThreadTaskIncompleteExecutionException {
while(!over) {
if(System.currentTimeMillis() - startTime > wautTime) {
//强制结束
exit = true;
if(!over) {
throw new ThreadTaskIncompleteExecutionException();
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
if(startTime == 0) {
throw new NotStartedException();
}
return System.currentTimeMillis() - startTime;
}
}
##发起者
发起者是一个工厂类,他的作用就是通过创建管理者的角色让它去执行你交给他的任务。其实很简单,不多说了,上代码:
package com.cnpany.common.util.thread.factory;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import com.cnpany.common.util.thread.ThreadRobot;
import com.cnpany.common.util.thread.ThreadTask;
import com.cnpany.common.util.thread.impl.BatchThread;
/**
* 线程任务工厂
* @author 郭胜凯
* @time 2017年10月28日 上午11:27:25
* @email guoshengkai@shoujinwang.com
*/
public class ThreadTaskFactory {
/**
* 创建批量任务线程池
* @param roboot
* 工作机器人
* @param maxThread
* 线程数
* @param quqe
* 消费队列
* @param param
* 机器人用到的参数
* @return
*/
public static<T, K> ThreadTask<T> createBatch(ThreadRobot<T, K> roboot, int maxThread, List<T> quqe, K param) {
return new BatchThread<T, K>(roboot, maxThread, quqe, new ReentrantLock(), param);
}
}
OK!到此为止,这个线程池就算是完工了,具体怎么去用呢,我们只需要通过ThreadTaskFactory来创建这么一个批处理任务,也就是传机器人(工作者)需要做的实现逻辑就好,这里我是直接这么做的:
/**
* 创建智享方案协议并签章
*/
@Scheduled(cron = "0/20 * * * * ? ")
public void doCreateIntelAfreement() {
logger.info("[智享方案协议]-----------------------------扫描待签章的序列");
List<String> accounts = commonSignService.listIntelAfreementAccounts();
if(!accounts.isEmpty()) {
//定义批量处理任务线程池
ThreadTask<String> batchSign = ThreadTaskFactory.createBatch(new ThreadRobot<String, CommonSignService>() {
@Override
public void excute(String accountId, CommonSignService service) {
CommonAttach attach = service.createIntelAfreementPdf(accountId); //生成文件
String afreementId = service.InsertIntelTenderAfreement(accountId, attach.getId()); //协议入库
service.signIntelAfreementPdf(afreementId); //协议签章
}
}, 20, accounts, commonSignService);
logger.info("[智享方案协议]-----------------------------开始批处理");
batchSign.start();
try {
batchSign.doWait(18000);
logger.info("[智享方案协议]-----------------------------批处理完毕");
} catch (ThreadTaskIncompleteExecutionException e) {
logger.info("[智享方案协议]-----------------------------超时未完成,批处理强制退出");
e.printStackTrace();
}
}
}