应用场景
在我们写业务代码中,经常会想到多线程处理复杂耗时任务,但又想返回处理进度,下面代码就是实现这个业务。
以下代码是执行两个任务,分别输出两个任务的实时进度和最后结果,包括定时从缓存移除
运行效果图:
类说明
需自定义类:
- MyRun---------------程序测试入口
- MyTask--------------具体需执行业务类
- RequestData--------请求参数实体
- ResponseRet-------返回结果实体
框架类(无需修改,直接用):
- PendingJobPool-----------框架的主体类,也是调用者主要使用的类
- CheckJobProcesser------任务完成后,在一定的时间供查询,之后为释放资源节约内存,需要定期处理过期的任务
- ITaskProcesser-------------要求框架使用者实现的任务接口,因为任务的性质在调用时才知道
- ItemDelayed----------------存放到延时队列的元素
- JobInfo-----------------------任务信息实体
- ResultEnum-----------------返回结果枚举类
- TaskResult------------------任务处理返回结果实体类
MyRun类:
package com.hyun.test.my;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import com.hyun.test.PendingJobPool;
import com.hyun.test.vo.JobInfo;
/**
* 类说明:模拟一个应用程序,提交工作和任务,并查询任务进度
* @author hyun
*
*/
public class MyRun {
public static void main(String[] args) throws Exception {
//模拟两个任务工作场景(例如两个人同时进行文件校验)
MyTask task = new MyTask();
String taskName = "test-1";
String taskNameTwo = "test-2";
//每个任务需要50个子任务(比如一个文件校验 可分为多个线程去校验)
int taskLength = 50;
//过期时间(比如页面需要查询校验进度,当页面不小心关了 可直接根据任务名称直接查询 不用再次校验,这个过期时间就是当当前未过期时能查询到)
int expiredTime =10;
//拿到框架的实例
PendingJobPool<ResponseRet, RequestData> pool = PendingJobPool.getPendingTaskPool(ResponseRet.class, RequestData.class);
PendingJobPool<ResponseRet, RequestData> poolTwo = PendingJobPool.getPendingTaskPool(ResponseRet.class, RequestData.class);
//注册job
pool.registerTask(taskLength, taskName, task, expiredTime, TimeUnit.SECONDS);
poolTwo.registerTask(taskLength, taskNameTwo, task, expiredTime, TimeUnit.SECONDS);
for(int i = 0; i < taskLength; i++) {
RequestData data = new RequestData();
data.setId(UUID.randomUUID().toString());
data.setName("name1-" + i);
//依次推入Task
pool.putTask(taskName, data);
}
for(int i = 0; i < taskLength; i++) {
RequestData data = new RequestData();
data.setId(UUID.randomUUID().toString());
data.setName("name2-" + i);
pool.putTask(taskNameTwo, data);
}
//以下为查询结果操作
Thread readData = new Thread(()->{
for(;;) {
JobInfo<ResponseRet, RequestData> result = pool.getJobInfoMap().get(taskName);
JobInfo<ResponseRet, RequestData> resultTwo = pool.getJobInfoMap().get(taskNameTwo);
System.out.println(resultTwo.getTaskName() + ":" + resultTwo.toString());
System.out.println(result.getTaskName() + ":" + result.toString());
if((result.sucessNum.get() + result.failNum.get()) >= taskLength &&
(resultTwo.sucessNum.get() + resultTwo.failNum.get()) >= taskLength) {
System.out.println(result.getTaskName() + ":" + result.getBlokingDeque().size() + ":" + result.getBlokingDequeDetail());
System.out.println(resultTwo.getTaskName() + ":" + resultTwo.getBlokingDeque().size() + ":" + resultTwo.getBlokingDequeDetail());
break;
}
Random random = new Random();
try {
Thread.sleep(random.nextInt(30));
} catch (Exception e) {
e.printStackTrace();
}
}
});
readData.start();
}
}
MyTask类:
package com.hyun.test.my;
import java.util.Random;
import com.hyun.test.vo.ITaskProcesser;
import com.hyun.test.vo.ResultEnum;
import com.hyun.test.vo.TaskResult;
/**
* 类说明:一个实际任务类,将数值加上一个随机数,并休眠随机时间 根据实际业务需修改类
* @author hyun
*
*/
public class MyTask implements ITaskProcesser<ResponseRet, RequestData>{
@Override
public TaskResult<ResponseRet> taskExecute(RequestData reqData) {
TaskResult<ResponseRet> result = new TaskResult<ResponseRet>();
String id = reqData.getId();
String name = reqData.getName();
Random rand = new Random();
int randNum = rand.nextInt(100);
//模拟实际操作 随机休眠100毫秒以内
try {
Thread.sleep(randNum);
//如果20>随机数>=0 当做失败
if(20 > randNum && randNum >= 0) {
result.setResult(ResultEnum.FAIL);
result.setReason("失败结果");
} else if(randNum >= 90) {//如果随机数>=90 当异常处理
result.setResult(ResultEnum.EXCEPTION);
result.setReason("异常信息");
} else {//其它情况当成功处理
result.setResult(ResultEnum.SUCESS);
result.setReason("Sucess");
ResponseRet responseRet = new ResponseRet();
responseRet.setResult(id);
result.setData(responseRet);
}
} catch (InterruptedException e) {
//异常处理
e.printStackTrace();
result.setResult(ResultEnum.EXCEPTION);
result.setReason(e.getMessage());
}
return result;
}
}
RequestData类:
package com.hyun.test.my;
/**
* 请求参数实体 根据业务需修改类
* @author hyun
*
*/
public class RequestData {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "id:" + id + ",name:" + name;
}
}
ResponseRet类:
package com.hyun.test.my;
/**
* 返回结果实体 根据需要业务需修改类
* @author hyun
*
*/
public class ResponseRet {
private String result;
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
@Override
public String toString() {
return "[result:" + result + "]";
}
}
PendingJobPool类:
package com.hyun.test;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import com.hyun.test.vo.ITaskProcesser;
import com.hyun.test.vo.ItemDelayed;
import com.hyun.test.vo.JobInfo;
import com.hyun.test.vo.ResultEnum;
import com.hyun.test.vo.TaskResult;
/**
* 框架的主体类,也是调用者主要使用的类
* @author hyun
*
* @param <R> 返回结果类型
* @param <T> 参数类型
*/
public class PendingJobPool<R, T> {
/**
* cpu核心数
*/
private final static int PROCESSOR_NUM = Runtime.getRuntime().availableProcessors() + 1;
/**
* 初始化线程数
*/
private static int INIT_THREAD_NUM = PROCESSOR_NUM;
/**
* keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating
*/
private static int ALIVE_TIME = 5;
/**
* 存放任务结果双端队列
*/
private static BlockingQueue<Runnable> bq = new ArrayBlockingQueue<>(2000);
/**
* 线程池,固定大小,有界队列
*/
private Executor executor = new ThreadPoolExecutor(INIT_THREAD_NUM, PROCESSOR_NUM, ALIVE_TIME, TimeUnit.SECONDS, bq);
/**
* job的存放容器
*/
public ConcurrentHashMap<String, JobInfo<R, T>> jobInfoMap = new ConcurrentHashMap<>();
/**
* 单例模式
* @author hyun
*
* @param <R> 结果类型
* @param <T> 参数类型
*/
private static class InitPendingSingleton<R, T>{
public static PendingJobPool<?,?> pendingTaskPool = new PendingJobPool<>();
}
@SuppressWarnings("unchecked")
public static <E,K>PendingJobPool<E,K> getPendingTaskPool(Class<?> E, Class<?> K) {
return (PendingJobPool<E,K>)InitPendingSingleton.pendingTaskPool;
}
/**
* 调用者注册工作,如工作名,任务的处理器等等
* @param taskLength
* @param taskName
* @param task
* @param expiredTime
* @param unit
* @throws Exception
*/
public void registerTask(int taskLength, String taskName, ITaskProcesser<R, T> task, long expiredTime, TimeUnit unit) throws Exception {
JobInfo<R, T> jobInfo = new JobInfo<R, T>(taskLength, taskName, task, expiredTime, unit);
JobInfo<R, T> oldJobInfo = this.jobInfoMap.putIfAbsent(jobInfo.getTaskName(), jobInfo);
if(oldJobInfo != null) {
throw new Exception("任务名称已存在");
}
}
/**
* 对工作中的任务进行包装,提交给线程池使用,并处理任务的结果,写入缓存以供查询
* @author hyun
*
* @param <R> 结果类型
* @param <T> 参数类型
*/
private static class TaskThread<R, T> implements Runnable {
final T data;
private JobInfo<R, T> jobInfo;
public TaskThread(JobInfo<R, T> jobInfo, T data) {
this.data = data;
this.jobInfo = jobInfo;
}
@Override
public void run() {
ITaskProcesser<R, T> taskProcesser = jobInfo.getTaskProcesser();
TaskResult<R> result;
try {
//调用业务人员实现的具体方法
result = taskProcesser.taskExecute(data);
//要做检查,防止开发人员处理不当
if (result == null) {
result = new TaskResult<R>(ResultEnum.EXCEPTION, "result is null");
}
if(ResultEnum.SUCESS.equals(result.getResult())) {
jobInfo.sucessNum.getAndIncrement();
} else {
jobInfo.failNum.getAndIncrement();
}
} catch (Exception e1) {
e1.printStackTrace();
jobInfo.failNum.getAndIncrement();
result = new TaskResult<R>(ResultEnum.EXCEPTION, e1.getMessage());
}
try {
jobInfo.getBlokingDeque().putLast(result);
if((jobInfo.sucessNum.get() + jobInfo.failNum.get()) == jobInfo.getTaskLength() ) {
ItemDelayed<String> delayed = new ItemDelayed<>(jobInfo.getExpiredTime(), jobInfo.getUnit(), jobInfo.getTaskName());
CheckJobProcesser.bq.put(delayed);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 调用者提交工作中的任务
* @param name
* @param data
*/
public void putTask(String name, T data) {
JobInfo<R, T> jobInfo = this.jobInfoMap.get(name);
executor.execute(new TaskThread<>(jobInfo, data));
}
public ConcurrentHashMap<String, JobInfo<R, T>> getJobInfoMap() {
return jobInfoMap;
}
public void setJobInfoMap(ConcurrentHashMap<String, JobInfo<R, T>> jobInfoMap) {
this.jobInfoMap = jobInfoMap;
}
}
CheckJobProcesser类:
package com.hyun.test;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import com.hyun.test.vo.ItemDelayed;
/**
* 类说明:任务完成后,在一定的时间供查询,之后为释放资源节约内存,需要定期处理过期的任务
* @author hyun
*
*/
public class CheckJobProcesser {
public static BlockingQueue<ItemDelayed<String>> bq = new DelayQueue<ItemDelayed<String>>();
private static class InitCheckJobProcesser {
public static CheckJobProcesser checkJobProcesser = new CheckJobProcesser();
}
/**
* 单例模式
* @return
*/
public static CheckJobProcesser getCheckJobProcesser() {
return InitCheckJobProcesser.checkJobProcesser;
}
static {
Thread thread = new Thread(new CheckJobThread());
thread.start();
}
/**
* 处理队列中到期任务的执行
* @author hyun
*
*/
private static class CheckJobThread implements Runnable {
@Override
public void run() {
try {
System.out.println("检查任务过期线程启动");
for(;;) {
ItemDelayed<String> removeObj = bq.take();
String taskName = removeObj.getData();
PendingJobPool<Object,Object> pendingTaskPool = PendingJobPool.getPendingTaskPool(Object.class, Object.class);
pendingTaskPool.getJobInfoMap().remove(taskName);
System.out.println("任务被移除:" + taskName);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
ITaskProcesser类:
package com.hyun.test.vo;
/**
*类说明:要求框架使用者实现的任务接口,因为任务的性质在调用时才知道,
*所以传入的参数和方法的返回值均使用泛型
* @author hyun
*
* @param <R> 返回结果类型
* @param <T> 参数类型
*/
public interface ITaskProcesser<R, T> {
/**
* @param data 调用方法需要使用的业务数据
* @return 方法执行后业务方法的结果
*/
public TaskResult<R> taskExecute(T data);
}
ItemDelayed类:
package com.hyun.test.vo;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
/**
* 存放到延时队列的元素
* @author hyun
*
* @param <T> 放入的数据类型
*/
public class ItemDelayed<T> implements Delayed {
/**
* 到期时间
*/
private long activeTime;
/**
* 数据实体
*/
private T data;
public ItemDelayed(long activeTime, TimeUnit unit, T data) {
this.data = data;
//将传入的时长转换为超时的时刻
this.activeTime = TimeUnit.NANOSECONDS.convert(activeTime, unit) + System.nanoTime();//将传入的时长转换为超时的时刻
}
@Override
public int compareTo(Delayed o) {
long diffTime = this.getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
return diffTime > 0 ? 1 : ((diffTime == 0 ? 0 : -1));
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(this.activeTime - System.nanoTime(), TimeUnit.NANOSECONDS);
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
JobInfo类:
package com.hyun.test.vo;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 任务信息实体
* @author hyun
*
* @param <R> 返回结果类型
* @param <T> 参数类型
*/
public class JobInfo<R, T> {
//工作的任务个数
private final int taskLength;
//区分唯一的工作
private final String taskName;
//成功处理的任务数
public final AtomicInteger sucessNum = new AtomicInteger(0);
//处理失败的任务数
public final AtomicInteger failNum = new AtomicInteger(0);
//这个工作的任务处理器
private final ITaskProcesser<R, T> taskProcesser;
//结果队列,拿结果从头拿,放结果从尾部放
private final LinkedBlockingDeque<TaskResult<R>> blokingDeque;
//工作的完成保存的时间,超过这个时间从缓存中清除
private final long expiredTime;
//过期时间单位
private final TimeUnit unit;
/**
* 阻塞队列不应该由调用者传入,应该内部生成,长度为工作的任务个数
* @param taskLength
* @param taskName
* @param taskProcesser
* @param expiredTime
* @param unit
*/
public JobInfo(int taskLength, String taskName, ITaskProcesser<R, T> taskProcesser, long expiredTime, TimeUnit unit) {
super();
this.taskLength = taskLength;
this.taskName = taskName;
this.sucessNum.set(0);
this.failNum.set(0);
this.taskProcesser = taskProcesser;
this.blokingDeque = new LinkedBlockingDeque<TaskResult<R>>(taskLength);
this.expiredTime = expiredTime;
this.unit = unit;
}
/**
* 返回当前任务明细的结果 根据实际修改
* @return
*/
public String getBlokingDequeDetail() {
StringBuffer sb = new StringBuffer();
for(;;) {
TaskResult<R> result = this.blokingDeque.pollFirst();
if(result == null) {
break;
}
sb.append(result.toString());
}
return sb.toString();
}
@Override
public String toString() {
return "Success[" + sucessNum.get() + "]/Fail[" + failNum.get() + "] Total[" + taskLength + "]";
}
public int getTaskLength() {
return taskLength;
}
public String getTaskName() {
return taskName;
}
public ITaskProcesser<R, T> getTaskProcesser() {
return taskProcesser;
}
public LinkedBlockingDeque<TaskResult<R>> getBlokingDeque() {
return blokingDeque;
}
public long getExpiredTime() {
return expiredTime;
}
public TimeUnit getUnit() {
return unit;
}
}
ResultEnum类:
package com.hyun.test.vo;
/**
* 返回结果枚举类
* @author hyun
*
*/
public enum ResultEnum {
//方法成功执行并返回了业务人员需要的结果
SUCESS,
//方法成功执行但是返回的是业务人员不需要的结果
FAIL,
//方法执行抛出了Exception
EXCEPTION;
}
TaskResult类:
package com.hyun.test.vo;
/**
* 任务处理返回结果实体类
* @author hyun
*
* @param <R> 返回结果类型
*/
public class TaskResult<R> {
/**
* 方法的业务结果数据;
*/
private R data;
/**
* 这里放方法失败的原因
*/
private String reason;
/**
* 方法本身运行是否正确的结果类型
*/
private ResultEnum result;
public TaskResult() {
}
public TaskResult(R data, String reason, ResultEnum result) {
super();
this.data = data;
this.reason = reason;
this.result = result;
}
public TaskResult(ResultEnum result, String reason) {
super();
this.reason = reason;
this.result = result;
}
/**
* 方便业务人员使用,这个构造方法表示业务方法执行成功返回的结果
* @param data
*/
public TaskResult(R data) {
super();
this.data = data;
this.reason = "sucess";
this.result = ResultEnum.SUCESS;
}
@Override
public String toString() {
return "[result:" + result.name() + ",reason:" + reason + ",data:" + (data != null ? data.toString() : "") + "] ";
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public ResultEnum getResult() {
return result;
}
public void setResult(ResultEnum result) {
this.result = result;
}
public void setData(R data) {
this.data = data;
}
public R getData() {
return data;
}
}