多线程任务处理器改进

1、问题介绍

在上一版本 的多线程处理PDF下载任务的过程中我们用到了多线程工作处理器。不知道读者有没有发现一个新的问题。就是虽然我们有定时线程定期清理超时任务。但是我们没有清理正在工作的线程。假设客户提交了一个非常耗时的任务,此处可以假设是死循环。但是用户又不想等待很长时间,所以就需要我们框架定期中断这些线程,退出无意义的工作中。

2、框架修改

工作任务处理类

首先我们修改任务处理接口,使得任务处理接口可以抛出中断异常。

/**
 *
 *
 *类说明:要求框架使用者实现的任务接口,因为任务的性质在调用时才知道,
 *所以传入的参数和方法的返回值均使用泛型
 */
public interface ITaskProcesserChen<T, R>{
	/**
	 * @param data 调用方法需要使用的业务数据
	 * @return 方法执行后业务方法的结果
	 */
	TaskResult<R> taskExecute(T data) throws InterruptedException;
}

工作任务信息类

在工作任务信息类中,将我们新的工作任务处理类引进来,完整代码如下:

package com.chen.mutithread.vo;

import com.chen.mutithread.CheckJobProcesser;
import com.chen.mutithread.CheckJobProcesserChen;

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


/**
 * 类说明:提交给框架执行的工作实体类,工作:表示本批次需要处理的同性质任务(Task)的一个集合
 * 注意:返回给用户的不能是引用,防止线程安全事故发生。即不允许用户修改底层框架使用的东西
 */
public class JobInfoChen<R> {
    //区分唯一的工作
    private final String jobName;
    //工作的任务个数
    private final int jobLength;
    //这个工作的任务处理器
    private final ITaskProcesserChen<?, ?> taskProcesser;
    //成功处理的任务数
    private final AtomicInteger successCount;
    //已处理的任务数
    private final AtomicInteger taskProcesserCount;
    //拿结果从头拿,放结果从尾部放 保存工作中每个任务的返回结果,只有处理完成成放进去
    // 注意这是一个结果队列
    private final LinkedBlockingDeque<TaskResult<R>> taskDetailQueue;
    //工作的完成保存的时间,超过这个时间从缓存中清除
    private final long expireTime;

    //阻塞队列不应该由调用者传入,应该内部生成,长度为工作的任务个数
    public JobInfoChen(String jobName, int jobLength,
                       ITaskProcesserChen<?, ?> 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<>(jobLength);
        this.expireTime = expireTime;
    }

    //返回成功处理的结果数
    //不能直接返回 successCount 因为这是一个引用,会导致线程不安全,以下同理
    public int getSuccessCount() {
        return successCount.get();
    }

    //返回当前已处理的结果数
    public int getTaskProcesserCount() {
        return taskProcesserCount.get();
    }

    //提供工作中失败的次数,为了方便调用者使用
    public int getFailCount() {
        return taskProcesserCount.get() - successCount.get();
    }

    //获得工作中每个任务的处理详情
    //注意由于阻塞队列是加锁的,所以不存在写的时候读,多线程冲突
    //由于是框架,不能讲引用传给用户,防止出现线程安全问题
    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;
    }

    //从业务应用角度来说,保证最终一致性即可,不需要对方法加锁.
    //注意 从jvm内存角度,只有传递进来的参数需要操作的时候才需要新建一个变量接受,即栈变量
    public void addTaskResult(TaskResult<R> result, CheckJobProcesserChen checkJob) {
        if (TaskResultType.Success.equals(result.getResultType())) {
            successCount.incrementAndGet();
        }
        taskDetailQueue.addLast(result);
        taskProcesserCount.incrementAndGet();

        //工作队列满了,放入过期队列
        //这里一般假设用户只是提交 jobLength 个任务到工作框架中,提交完毕定期清除
        if (taskProcesserCount.get() == jobLength) {
            checkJob.putJob(jobName, expireTime);
        }


    }

    public String getTotalProcess() {
        return "Success[" + successCount.get() + "]/Current["
                + taskProcesserCount.get() + "] Total[" + jobLength + "]";
    }

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


}

工作任务线程池类

在工作任务线程池类中,将工作任务信息类和工作任务处理类同步更新过来,源码如下:

package com.chen.mutithread;

import com.chen.mutithread.vo.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;


/**
 * 框架的主体类,也是调用者主要使用的类
 */
public class PendingJobPoolChen {

    //保守估计
    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的存放容器
    //G<?> 与 G<? extends Object>等同,如List<?> 与List<? extends Objext>等同。
    private static ConcurrentHashMap<String, JobInfoChen<?>> jobInfoMap
            = new ConcurrentHashMap<>();

    //线程和处理任务对应
    private static ConcurrentHashMap<String, List<?>> threadMap = new ConcurrentHashMap<>();
//    private static ConcurrentHashMap<String, List<?>> threadMap2 = new ConcurrentHashMap<>();


    public static ConcurrentHashMap<String, List<?>> getThreadMap() {
        return threadMap;
    }

    private static CheckJobProcesserChen checkJob
            = CheckJobProcesserChen.getInstance();

    public static Map<String, JobInfoChen<?>> getMap() {
        return jobInfoMap;
    }

    //单例模式------注意:限制用户使用的线程数,防止应用崩掉
    private PendingJobPoolChen() {
    }

    //懒加载式的线程安全
    private static class JobPoolHolder {
        public static PendingJobPoolChen pool = new PendingJobPoolChen();
    }

    public static PendingJobPoolChen getInstance() {
        return JobPoolHolder.pool;
    }
    //单例模式------

    //对工作中的任务进行包装,提交给线程池使用,并处理任务的结果,写入缓存以供查询
    public static class PendingTask<T, R> implements Runnable {

        private JobInfoChen<R> jobInfo;
        private T processData;
        private Thread currentThread;

        public PendingTask(JobInfoChen<R> jobInfo, T processData) {
            super();
            this.jobInfo = jobInfo;
            this.processData = processData;
            this.currentThread = Thread.currentThread();
        }

        public void quit() {
            this.currentThread.interrupt();
        }

        //告诉编译器忽略指定的警告,不用在编译完成后出现警告信息。
        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            R r = null;
            ITaskProcesserChen<T, R> taskProcesser =
                    (ITaskProcesserChen<T, R>) jobInfo.getTaskProcesser();
            TaskResult<R> result = null;

            //用户的代码是最不可信的,所以防止用户程序抛出异常,需要try catch处理
            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 (InterruptedException e) {
                System.out.println("线程超时终止!");
            } catch (Exception e) {
                e.printStackTrace();
                result = new TaskResult<R>(TaskResultType.Exception, r,
                        e.getMessage());
            } finally {
                jobInfo.addTaskResult(result, checkJob);
            }
        }
    }

    //根据工作名称检索工作
    @SuppressWarnings("unchecked")
    private <R> JobInfoChen<R> getJob(String jobName) {
        JobInfoChen<R> jobInfo = (JobInfoChen<R>) jobInfoMap.get(jobName);
        if (null == jobInfo) {
            throw new RuntimeException(jobName + "是个非法任务、或该任务已经过期!");
        }
        return jobInfo;
    }

    public <T, R> void putTaskAndThread(String jobName, PendingTask<T, R> task) {
        ArrayList<PendingTask<T, R>> pendingTasks = new ArrayList<>();
        pendingTasks.add(task);
        //添加任务,将任务和线程绑定
        List<PendingTask<T, R>> objects = (List<PendingTask<T, R>>) threadMap.putIfAbsent(jobName, pendingTasks);
        if (objects != null) {
            objects.addAll(pendingTasks);
            // 叠加到已有任务末尾
            threadMap.put(jobName, objects);
        }

    }

    //调用者提交工作中的任务
    public <T, R> void putTask(String jobName, T t) {
        JobInfoChen<R> jobInfo = getJob(jobName);
        PendingTask<T, R> task = new PendingTask<>(jobInfo, t);

        //添加任务,将任务和线程绑定
        putTaskAndThread(jobName, task);

        taskExecutor.execute(task);
//        Future<?> future = taskExecutor.submit(task);
    }

    //调用者注册工作,如工作名,任务的处理器等等,名称唯一,相当于ID
    public <R> void registerJob(String jobName, int jobLength,
                                ITaskProcesserChen<?, ?> taskProcesser, long expireTime) {
        JobInfoChen<R> jobInfo = new JobInfoChen<>(jobName, jobLength, taskProcesser, expireTime);
        // 不能使用put,防止任务在处理的时候,用户修改map,导致处理异常
        if (jobInfoMap.putIfAbsent(jobName, jobInfo) != null) {
            throw new RuntimeException(jobName + "已经注册了!");
        }
    }

    //获得每个任务的处理详情
    public <R> List<TaskResult<R>> getTaskDetail(String jobName) {
        JobInfoChen<R> jobInfo = getJob(jobName);
        return jobInfo.getTaskDetail();
    }

    //获得工作的整体处理进度
    public <R> String getTaskProgess(String jobName) {
        JobInfoChen<R> jobInfo = getJob(jobName);
        return jobInfo.getTotalProcess();
    }

}

其中特别要注意的是下面这段代码:

 //对工作中的任务进行包装,提交给线程池使用,并处理任务的结果,写入缓存以供查询
    public static class PendingTask<T, R> implements Runnable {

        private JobInfoChen<R> jobInfo;
        private T processData;
        private Thread currentThread;

        public PendingTask(JobInfoChen<R> jobInfo, T processData) {
            super();
            this.jobInfo = jobInfo;
            this.processData = processData;
            this.currentThread = Thread.currentThread();
        }

        public void quit() {
            this.currentThread.interrupt();
        }
//...省略其他代码
}

quit()函数中,我们不能直接使用Thread.currentThread().interrupt();必须在线程初始化的时候用成员变量保存下来。否则终止的就不是工作处理线程。而是定时清理线程。

public <T, R> void putTaskAndThread(String jobName, PendingTask<T, R> task) {
        ArrayList<PendingTask<T, R>> pendingTasks = new ArrayList<>();
        pendingTasks.add(task);
        //添加任务,将任务和线程绑定
        List<PendingTask<T, R>> objects = (List<PendingTask<T, R>>) threadMap.putIfAbsent(jobName, pendingTasks);
        if (objects != null) {
            objects.addAll(pendingTasks);
            // 叠加到已有任务末尾
            threadMap.put(jobName, objects);
        }

    }

putTaskAndThread()函数我们也要注意一下,不要以为,啊我们使用了ConcurrentHashMap,之后就会线程安全,然后就万事大吉了。这里有个细节需要注意一下(这个坑我已经踩过)。我们一定要使用putIfAbsent方法进行判断是否已经包含了此类工作,如果包含将新任务添加到队列再添加。否则直接put()会覆盖之前的List。

清理任务类

清理任务类主要添加了将已删除工作任务中,仍然在运行的线程,将这部分线程清理掉,释放系统资源。

package com.chen.mutithread;

import com.chen.mutithread.vo.ItemVo;

import java.util.List;
import java.util.concurrent.DelayQueue;


/**
 * 类说明:任务完成后,在一定的时间供查询,之后为释放资源节约内存,需要定期处理过期的任务
 */
public class CheckJobProcesserChen {
    //DelayQueue是一个无界阻塞队列,只有在延迟期满时才能从中提取元素
    private static DelayQueue<ItemVo<String>> queue
            = new DelayQueue<>();//存放已完成任务等待过期的队列

    //单例模式------
    private CheckJobProcesserChen() {
    }

    private static class ProcesserHolder {
        public static CheckJobProcesserChen processer = new CheckJobProcesserChen();
    }

    public static CheckJobProcesserChen getInstance() {
        return ProcesserHolder.processer;
    }
    //单例模式------

    //处理队列中到期任务的实行
    private static class FetchJob implements Runnable {

        @Override
        public void run() {
            while (true) {
                try {
                    //拿到已经过期的任务 take()是阻塞方法
                    ItemVo<String> item = queue.take();
                    String jobName = item.getDate();
                    //删除工作信息
                    PendingJobPoolChen.getMap().remove(jobName);
                    //中断正在工作的任务线程
                    shutDownThread(jobName);
                    System.out.println(jobName + " is out of date,remove from map!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static <T, R> void shutDownThread(String jobName) {
        List<?> objects = PendingJobPoolChen.getThreadMap().get(jobName);
        if (objects == null) {
            return;
        }

        List<PendingJobPoolChen.PendingTask<T, R>> threads = (List<PendingJobPoolChen.PendingTask<T, R>>) objects;

        for (PendingJobPoolChen.PendingTask<T, R> thread : threads) {
            //  逐个终止,即使线程运行结束也没有影响
            thread.quit();
        }
        //删除
        PendingJobPoolChen.getThreadMap().remove(jobName);

    }

    /*任务完成后,放入队列,经过expireTime时间后,从整个框架中移除*/
    public void putJob(String jobName, long expireTime) {
        //实现名称和到期时间绑定
        ItemVo<String> item = new ItemVo<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("开启任务过期检查守护线程................");
    }


}


这里需要注意的是:shutDownThread()方法

public static <T, R> void shutDownThread(String jobName) {
        List<?> objects = PendingJobPoolChen.getThreadMap().get(jobName);
        if (objects == null) {
            return;
        }

        List<PendingJobPoolChen.PendingTask<T, R>> threads = (List<PendingJobPoolChen.PendingTask<T, R>>) objects;

        for (PendingJobPoolChen.PendingTask<T, R> thread : threads) {
            //  逐个终止,即使线程运行结束也没有影响
            thread.quit();
        }
        //删除
        PendingJobPoolChen.getThreadMap().remove(jobName);

    }

最后一行代码,一定要清除PendingJobPoolChen.getThreadMap().remove(jobName);,否则会导致系统内存泄露。

3、总结:

多线程任务中一定要注意线程的管理和内存泄露。一旦出现没有意义的线程,立刻停止该线程释放资源。不需要的对象尽早删除,防止遗忘而出现内存泄露。

发布了22 篇原创文章 · 获赞 4 · 访问量 22万+

猜你喜欢

转载自blog.csdn.net/qq_32510597/article/details/105302358