7、并发任务执行框架

一、需求分析

举例

互联网教育中,会定期为每个班级或者年级的学生生成近期成绩考核的execl表单用来分析教育的质量,为每个学生生成表单是一个比较费时间的操作,假如一个任务需要3s,如果需要给一个年级1000名学生批量生成,单线程耗时可能会超过1小时,响应已经非常慢了。

对于不熟悉java并发编程的开发人员或者刚开始接触java的小伙伴可能不会使用并发来解决类似的问题,这时候就需要提供一种并发框架来供业务开发人员使用。尽可能的屏蔽并发的一些细节,使得框架的使用尽可能的"友好"一些。

需求痛点

1、都有批量任务要完成且速度慢

2、都要求可以查询进度

3、在使用上尽可能的对业务开发人员友好

需要做的事情

提高性能,采用多线程,屏蔽细节

封装线程池和阻塞队列

每个批量任务拥有自己的上下文环境

需要一个并发安全的容器保存每个任务

自动清除已完成和过期任务

定时轮询?

二、框架流程图

在这里插入图片描述

三、具体实现

package cn.enjoy.controller.BingfaKuangjia;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author wangle25
 * @description  Job工作类
 * @date 22:57 2020-04-12
 * @param
 * @return
 **/

public class JobInfo<R> {
	//区分唯一的工作
	private final String jobName;
	//工作的任务个数
	private final int jobLength;
	//这个工作的任务处理器
	private final ITaskProcesser<?,?> taskProcesser;
	//成功处理的任务数
	private final AtomicInteger  successCount;
	//已处理的任务数
	private final AtomicInteger  taskProcesserCount;
	//结果队列,拿结果从头拿,放结果从尾部放
	private final LinkedBlockingDeque<TaskResult<R>> taskDetailQueue;
	//工作的完成保存的时间,超过这个时间从缓存中清除
	private final long expireTime;
	
	//与课堂上有不同,修订为,阻塞队列不应该由调用者传入,应该内部生成,长度为工作的任务个数
	public JobInfo(String jobName, int jobLength, 
			ITaskProcesser<?, ?> taskProcesser,
			long expireTime) {
		super();
		this.jobName = jobName;
		this.jobLength = jobLength;
		this.taskProcesser = taskProcesser;
		this.successCount = new AtomicInteger(0);
		this.taskProcesserCount = new AtomicInteger(0);
		this.taskDetailQueue = new LinkedBlockingDeque<TaskResult<R>>(jobLength);;
		this.expireTime = expireTime;
	}

	public ITaskProcesser<?, ?> getTaskProcesser() {
		return taskProcesser;
	}

	//返回成功处理的结果数
	public int getSuccessCount() {
		return successCount.get();
	}

	//返回当前已处理的结果数
	public int getTaskProcesserCount() {
		return taskProcesserCount.get();
	}
	
	//提供工作中失败的次数,课堂上没有加,只是为了方便调用者使用
	public int getFailCount() {
		return taskProcesserCount.get() - successCount.get();
	}
	
	public String getTotalProcess() {
		return "Success["+successCount.get()+"]/Current["
				+taskProcesserCount.get()+"] Total["+jobLength+"]";
	}
	
	//获得工作中每个任务的处理详情
	public List<TaskResult<R>> getTaskDetail(){
		List<TaskResult<R>> taskList = new LinkedList<>();
		TaskResult<R> taskResult;
		//从阻塞队列中拿任务的结果,反复取,一直取到null为止,说明目前队列中最新的任务结果已经取完,可以不取了
		while((taskResult=taskDetailQueue.pollFirst())!=null) {
			taskList.add(taskResult);
		}
		return taskList;
	}
	
	//放任务的结果,从业务应用角度来说,保证最终一致性即可,不需要对方法加锁.
	public void addTaskResult(TaskResult<R> result,CheckJobProcesser checkJob) {
		//执行成功,执行成功次数加一
		if (TaskResultType.Success.equals(result.getResultType())) {
			successCount.incrementAndGet();
		}
		taskDetailQueue.addLast(result);
		//task执行次数加一
		taskProcesserCount.incrementAndGet();

		//如果任务数已经执行完成,将任务加入到过期队列中
		if(taskProcesserCount.get()==jobLength) {
			checkJob.putJob(jobName, expireTime);
		}
		
	}

}
package cn.enjoy.controller.BingfaKuangjia;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;



/**
 * @author wangle25
 * @description //框架的主体类,也是调用者主要使用的类
 * @date 23:00 2020-04-12
 * @param 
 * @return 
 **/

public class PendingJobPool {
	
	//保守估计
	private static final int THREAD_COUNTS = 
			Runtime.getRuntime().availableProcessors();
	//任务队列
	private static BlockingQueue<Runnable> taskQueue
	 = new ArrayBlockingQueue<>(5000);
	//线程池,固定大小,有界队列
	private static ExecutorService taskExecutor = 
			new ThreadPoolExecutor(THREAD_COUNTS, THREAD_COUNTS, 60, 
					TimeUnit.SECONDS, taskQueue);
	//job的存放容器
	private static ConcurrentHashMap<String, JobInfo<?>> jobInfoMap
	   = new  ConcurrentHashMap<>();

	//job的过期任务队列,过期后会将job从job容器中移除
	private static CheckJobProcesser checkJob
	 	= CheckJobProcesser.getInstance();
	
	public static Map<String, JobInfo<?>> getMap(){
		return jobInfoMap;
	}
	
	//单例模式------
	private PendingJobPool() {}
	
	private static class JobPoolHolder{
		public static PendingJobPool pool = new PendingJobPool();
	}
	
	public static PendingJobPool getInstance() {
		return JobPoolHolder.pool;
	}
	//单例模式------
	
	//对工作中的任务进行包装,提交给线程池使用,并处理任务的结果,写入缓存以供查询
	private static class PendingTask<T,R> implements Runnable{
		
		private JobInfo<R> jobInfo;
		private T processData;

		public PendingTask(JobInfo<R> jobInfo, T processData) {
			super();
			//task对应的具体job
			this.jobInfo = jobInfo;
			//当前task的业务参数
			this.processData = processData;
		}

		@SuppressWarnings("unchecked")
		@Override
		public void run() {
			R r = null;
			ITaskProcesser<T,R> taskProcesser =
					(ITaskProcesser<T, R>) jobInfo.getTaskProcesser();
			TaskResult<R> result = null;
			
			try {
				//调用业务人员实现的具体方法
				result = taskProcesser.taskExecute(processData);
				//要做检查,防止开发人员处理不当
				if (result == null) {
					result = new TaskResult<R>(TaskResultType.Exception, r, 
							"result is null");
				}
				if (result.getResultType() == null) {
					if (result.getReason() == null) {
						result = new TaskResult<R>(TaskResultType.Exception, r, "reason is null");
					} else {
						result = new TaskResult<R>(TaskResultType.Exception, r,
								"result is null,but reason:" + result.getReason());
					}
				} 
			} catch (Exception e) {
				e.printStackTrace();
				result = new TaskResult<R>(TaskResultType.Exception, r, 
						e.getMessage());				
			}finally {
				//将task执行结果添加到job中的task执行结果中去,以便调用发查询
				jobInfo.addTaskResult(result,checkJob);
			}
		}
	}
	
	//根据工作名称检索工作
	@SuppressWarnings("unchecked")
	private <R> JobInfo<R> getJob(String jobName){
		JobInfo<R> jobInfo = (JobInfo<R>) jobInfoMap.get(jobName);
		if(null==jobInfo) {
			throw new RuntimeException(jobName+"是个非法任务。");
		}
		return jobInfo;
	}
	
	//调用者提交工作中的任务
	public <T,R> void putTask(String jobName,T t) {
		//获取到容器中的Job
		JobInfo<R> jobInfo = getJob(jobName);
		//将业务方实现的任务执行接口封装成Runnable提交给线程池处理
		PendingTask<T,R> task = new PendingTask<T,R>(jobInfo,t);
		taskExecutor.execute(task);
	}
	
	//调用者注册工作,如工作名,任务的处理器等等
	public <R> void registerJob(String jobName,int jobLength,
			ITaskProcesser<?, ?> taskProcesser,long expireTime) {
		JobInfo<R> jobInfo = new JobInfo(jobName,jobLength,
				taskProcesser,expireTime);
		if (jobInfoMap.putIfAbsent(jobName, jobInfo)!=null) {
			throw new RuntimeException(jobName+"已经注册了!");
		}
	}
	
	//获得每个任务的处理详情
	public <R> List<TaskResult<R>> getTaskDetail(String jobName){
		JobInfo<R> jobInfo = getJob(jobName);
		return jobInfo.getTaskDetail();
	}
	
	//获得工作的整体处理进度
	public <R> String getTaskProgess(String jobName) {
		JobInfo<R> jobInfo = getJob(jobName);
		return jobInfo.getTotalProcess();	
	}
	
}
package cn.enjoy.controller.BingfaKuangjia;

import cn.enjoy.controller.thread.DelayQueue.DelayQueueValue;

import java.util.Map;
import java.util.concurrent.DelayQueue;



/**
 * @author wangle25
 * @description //任务完成后,在一定的时间供查询,之后为释放资源节约内存,需要定期处理过期的任务
 * @date 23:01 2020-04-12
 * @param 
 * @return 
 **/

public class CheckJobProcesser {
    private static DelayQueue<DelayQueueValue<String>> queue
    	= new DelayQueue<DelayQueueValue<String>>();//存放已完成任务等待过期的队列
    
	//单例模式------
	private CheckJobProcesser() {}
	
	private static class ProcesserHolder{
		public static CheckJobProcesser processer = new CheckJobProcesser();
	}
	
	public static CheckJobProcesser getInstance() {
		return ProcesserHolder.processer;
	}
	//单例模式------    
    
    //处理队列中到期任务的实行
    private static class FetchJob implements Runnable{

		@Override
		public void run() {
			while(true) {
				try {
					//拿到已经过期的任务
					DelayQueueValue<String> item = queue.take();
					String jobName =  (String)item.getData();
					PendingJobPool.getMap().remove(jobName);
					System.out.println(jobName+" is out of date,remove from map!");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}			
		}
    }
    
    /*任务完成后,放入队列,经过expireTime时间后,从整个框架中移除*/
    public void putJob(String jobName,long expireTime) {
		DelayQueueValue<String> item = new DelayQueueValue<String>(expireTime,jobName);
    	queue.offer(item);
    	System.out.println("Job["+jobName+"已经放入了过期检查缓存,过期时长:"+expireTime);
    }

    //静态方法、类初始化的时候就启动过期检查线程,因为当前线程和服务的时间期是一样的,所以设置为守护线程
    static {
    	Thread thread = new Thread(new FetchJob());
    	thread.setDaemon(true);
    	thread.start();
    	System.out.println("开启任务过期检查守护线程................");
    }
    
    
}

主要是提供

  • 使用并发来执行任务下的子task
  • 用户可查询任务的进度、成功的次数、执行的次数占比等。这就要求我们得保存任务,但是又不能无限保存下去,所以需要定期清除job任务(当任务中的task都执行完成后)。

注:此框架的学习和代码结构参考于享学课堂的mark老师公开课。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值