在多线程编程过程中,遇到这样的情况,主线程需要等待多个子线程的处理结果,才能继续运行下去。个人给这样的子线程任务取了个名字叫并行任务。对于这种任务,每次去编写代码加锁控制时序,觉得太麻烦,正好朋友提到CountDownLatch这个类,于是用它来编写了个小工具。

  首先,要处理的是多个任务,于是定义了一个接口

package com.zyj.thread;

import com.zyj.exception.ChildThreadException;

/**
* 多任务处理
* @author zengyuanjun
*/
public interface MultiThreadHandler {
/**
* 添加任务
* @param tasks
*/
void addTask(Runnable... tasks);
/**
* 执行任务
* @throws ChildThreadException
*/
void run() throws ChildThreadException;
}

  要处理的是并行任务,需要用到CountDownLatch来统计所有子线程执行结束,还要一个集合记录所有任务,另外加上我自定义的ChildThreadException类来记录子线程中的异常,通知主线程是否所有子线程都执行成功,便得到了下面这个抽象类AbstractMultiParallelThreadHandler。在这个类中,我顺便完成了addTask这个方法。

package com.zyj.thread.parallel;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch; import com.zyj.exception.ChildThreadException;
import com.zyj.thread.MultiThreadHandler; /**
* 并行线程处理
* @author zengyuanjun
*/
public abstract class AbstractMultiParallelThreadHandler implements MultiThreadHandler {
/**
* 子线程倒计数锁
*/
protected CountDownLatch childLatch; /**
* 任务列表
*/
protected List<Runnable> taskList; /**
* 子线程异常
*/
protected ChildThreadException childThreadException; public AbstractMultiParallelThreadHandler() {
taskList = new ArrayList<Runnable>();
childThreadException = new ChildThreadException();
} public void setCountDownLatch(CountDownLatch latch) {
this.childLatch = latch;
} /**
* {@inheritDoc}
*/
@Override
public void addTask(Runnable... tasks) {
if (null == tasks) {
taskList = new ArrayList<Runnable>();
}
for (Runnable task : tasks) {
taskList.add(task);
}
} /**
* {@inheritDoc}
*/
@Override
public abstract void run() throws ChildThreadException; }

  具体的实现,则是下面这个类。实现原理也很简单,主线程根据并行任务数创建一个CountDownLatch,传到子线程中,并运行所有子线程,然后await等待。子线程执行结束后调用CountDownLatch的countDown()方法,当所有子线程执行结束后,CountDownLatch计数清零,主线程被唤醒继续执行。

package com.zyj.thread.parallel;

import java.util.concurrent.CountDownLatch;

import com.zyj.exception.ChildThreadException;

/**
* 并行任务处理工具
*
* @author zengyuanjun
*
*/
public class MultiParallelThreadHandler extends AbstractMultiParallelThreadHandler { /**
* 无参构造器
*/
public MultiParallelThreadHandler() {
super();
} /**
* 根据任务数量运行任务
*/
@Override
public void run() throws ChildThreadException {
if (null == taskList || taskList.size() == 0) {
return;
} else if (taskList.size() == 1) {
runWithoutNewThread();
} else if (taskList.size() > 1) {
runInNewThread();
}
} /**
* 新建线程运行任务
*
* @throws ChildThreadException
*/
private void runInNewThread() throws ChildThreadException {
childLatch = new CountDownLatch(taskList.size());
childThreadException.clearExceptionList();
for (Runnable task : taskList) {
invoke(new MultiParallelRunnable(new MultiParallelContext(task, childLatch, childThreadException)));
}
taskList.clear();
try {
childLatch.await();
} catch (InterruptedException e) {
childThreadException.addException(e);
}
throwChildExceptionIfRequired();
} /**
* 默认线程执行方法
*
* @param command
*/
protected void invoke(Runnable command) {
if(command.getClass().isAssignableFrom(Thread.class)){
Thread.class.cast(command).start();
}else{
new Thread(command).start();
}
} /**
* 在当前线程中直接运行
*
* @throws ChildThreadException
*/
private void runWithoutNewThread() throws ChildThreadException {
try {
taskList.get(0).run();
} catch (Exception e) {
childThreadException.addException(e);
}
throwChildExceptionIfRequired();
} /**
* 根据需要抛出子线程异常
*
* @throws ChildThreadException
*/
private void throwChildExceptionIfRequired() throws ChildThreadException {
if (childThreadException.hasException()) {
childExceptionHandler(childThreadException);
}
} /**
* 默认抛出子线程异常
* @param e
* @throws ChildThreadException
*/
protected void childExceptionHandler(ChildThreadException e) throws ChildThreadException {
throw e;
} }

  并行任务是要运行的子线程,只要实现Runnable接口就行,并没有CountDownLatch对象,所以我用MultiParallelRunnable类对它封装一次,MultiParallelRunnable类里有个属性叫 MultiParallelContext,MultiParallelContext里面就是保存的子线程task、倒计数锁CountDownLatch和ChildThreadException这些参数。MultiParallelRunnable类完成运行子线程、记录子线程异常和倒计数锁减一。

package com.zyj.thread.parallel;

/**
* 并行线程对象
*
* @author zengyuanjun
*
*/
public class MultiParallelRunnable implements Runnable {
/**
* 并行任务参数
*/
private MultiParallelContext context; /**
* 构造函数
* @param context
*/
public MultiParallelRunnable(MultiParallelContext context) {
this.context = context;
} /**
* 运行任务
*/
@Override
public void run() {
try {
context.getTask().run();
} catch (Exception e) {
e.printStackTrace();
context.getChildException().addException(e);
} finally {
context.getChildLatch().countDown();
}
} }
package com.zyj.thread.parallel;

import java.util.concurrent.CountDownLatch;

import com.zyj.exception.ChildThreadException;

/**
* 并行任务参数
* @author zengyuanjun
*
*/
public class MultiParallelContext {
/**
* 运行的任务
*/
private Runnable task;
/**
* 子线程倒计数锁
*/
private CountDownLatch childLatch;
/**
* 子线程异常
*/
private ChildThreadException childException; public MultiParallelContext() {
} public MultiParallelContext(Runnable task, CountDownLatch childLatch, ChildThreadException childException) {
this.task = task;
this.childLatch = childLatch;
this.childException = childException;
} public Runnable getTask() {
return task;
}
public void setTask(Runnable task) {
this.task = task;
}
public CountDownLatch getChildLatch() {
return childLatch;
}
public void setChildLatch(CountDownLatch childLatch) {
this.childLatch = childLatch;
}
public ChildThreadException getChildException() {
return childException;
}
public void setChildException(ChildThreadException childException) {
this.childException = childException;
} }

  这里提一下ChildThreadException这个自定义异常,跟普通异常不一样,我在里面加了个List<Exception> exceptionList,用来保存子线程的异常。因为有多个子线程,抛出的异常可能有多个。

package com.zyj.exception;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import com.zyj.exception.util.ExceptionMessageFormat;
import com.zyj.exception.util.factory.ExceptionMsgFormatFactory; /**
* 子线程异常,子线程出现异常时抛出
* @author zengyuanjun
*/
public class ChildThreadException extends Exception { /**
* serialVersionUID
*/
private static final long serialVersionUID = 5682825039992529875L;
/**
* 子线程的异常列表
*/
private List<Exception> exceptionList;
/**
* 异常信息格式化工具
*/
private ExceptionMessageFormat formatter;
/**
* 锁
*/
private Lock lock; public ChildThreadException() {
super();
initial();
} public ChildThreadException(String message) {
super(message);
initial();
} public ChildThreadException(String message, StackTraceElement[] stackTrace) {
this(message);
setStackTrace(stackTrace);
} private void initial() {
exceptionList = new ArrayList<Exception>();
lock = new ReentrantLock();
formatter = ExceptionMsgFormatFactory.getInstance().getFormatter(ExceptionMsgFormatFactory.STACK_TRACE);
} /**
* 子线程是否有异常
* @return
*/
public boolean hasException() {
return exceptionList.size() > 0;
} /**
* 添加子线程的异常
* @param e
*/
public void addException(Exception e) {
try {
lock.lock();
e.setStackTrace(e.getStackTrace());
exceptionList.add(e);
} finally {
lock.unlock();
}
} /**
* 获取子线程的异常列表
* @return
*/
public List<Exception> getExceptionList() {
return exceptionList;
} /**
* 清空子线程的异常列表
*/
public void clearExceptionList() {
exceptionList.clear();
} /**
* 获取所有子线程异常的堆栈跟踪信息
* @return
*/
public String getAllStackTraceMessage() {
StringBuffer sb = new StringBuffer();
for (Exception e : exceptionList) {
sb.append(e.getClass().getName());
sb.append(": ");
sb.append(e.getMessage());
sb.append("\n");
sb.append(formatter.formate(e));
}
return sb.toString();
} /**
* 打印所有子线程的异常的堆栈跟踪信息
*/
public void printAllStackTrace() {
printAllStackTrace(System.err);
} /**
* 打印所有子线程的异常的堆栈跟踪信息
* @param s
*/
public void printAllStackTrace(PrintStream s) {
for (Exception e : exceptionList) {
e.printStackTrace(s);
}
} }

  ·有没有问题试一下才知道,写了个类来测试:TestCase 为并行任务子线程,resultMap为并行任务共同完成的结果集。假设resultMap由5部分组成,main方法中启动5个子线程分别完成一个部分,等5个子线程处理完后,main方法将结果resultMap打印出来。

package com.zyj.thread.test;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import com.zyj.exception.ChildThreadException;
import com.zyj.thread.MultiThreadHandler;
import com.zyj.thread.parallel.MultiParallelThreadHandler;
import com.zyj.thread.parallel.ParallelTaskWithThreadPool; public class TestCase implements Runnable { private String name;
private Map<String, Object> result; public TestCase(String name, Map<String, Object> result) {
this.name = name;
this.result = result;
} @Override
public void run() {
// 模拟线程执行1000ms
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 模拟线程1和线程3抛出异常
// if(name.equals("1") || name.equals("3"))
// throw new RuntimeException(name + ": throw exception");
result.put(name, "complete part " + name + "!");
} public static void main(String[] args) { System.out.println("main begin \t=================");
Map<String, Object> resultMap = new HashMap<String, Object>(8, 1);
MultiThreadHandler handler = new MultiParallelThreadHandler();
// ExecutorService service = Executors.newFixedThreadPool(3);
// MultiThreadHandler handler = new ParallelTaskWithThreadPool(service);
TestCase task = null;
// 启动5个子线程作为要处理的并行任务,共同完成结果集resultMap
for(int i=1; i<=5 ; i++){
task = new TestCase("" + i, resultMap);
handler.addTask(task);
}
try {
handler.run();
} catch (ChildThreadException e) {
System.out.println(e.getAllStackTraceMessage());
} System.out.println(resultMap);
// service.shutdown();
System.out.println("main end \t=================");
}
}

  运行main方法,测试结果如下

main begin     =================
{3=complete part 3!, 2=complete part 2!, 1=complete part 1!, 5=complete part 5!, 4=complete part 4!}
main end =================

  将模拟线程1和线程3抛出异常的注释打开,测试结果如下

红色的打印是子线程中捕获异常打印的堆栈跟踪信息,黑色的异常信息是主线程main方法中打印的,这说明主线程能够监视到子线程的出错,以便采取对应的处理。由于线程1和线程3出现了异常,未能完成任务,所以打印的resultMap只有第2、4、5三个部分完成。

  为了便于扩展,我把MultiParallelThreadHandler类中的invoke方法和childExceptionHandler方法定义为protected类型。invoke方法中是具体的线程执行,childExceptionHandler方法是子线程抛出异常后的处理,可以去继承,重写为自己想要的,比如我想用线程池去运行子线程,就可以去继承并重写invoke方法,得到下面的这个类

package com.zyj.thread.parallel;

import java.util.concurrent.ExecutorService;

/**
* 使用线程池运行并行任务
* @author zengyuanjun
*
*/
public class ParallelTaskWithThreadPool extends MultiParallelThreadHandler {
private ExecutorService service; public ParallelTaskWithThreadPool() {
} public ParallelTaskWithThreadPool(ExecutorService service) {
this.service = service;
} public ExecutorService getService() {
return service;
} public void setService(ExecutorService service) {
this.service = service;
} /**
* 使用线程池运行
*/
@Override
protected void invoke(Runnable command) {
if(null != service){
service.execute(command);
}else{
super.invoke(command);
}
} }

  测试就在上面的测试类中,只不过被注释掉了,测试结果是一样的,就不多说了。

  最后附上源码地址:https://github.com/zengyuanjun8/ThreadUtil

java多线程 - 处理并行任务的更多相关文章

  1. Java多线程 阻塞队列和并发集合

    转载:大关的博客 Java多线程 阻塞队列和并发集合 本章主要探讨在多线程程序中与集合相关的内容.在多线程程序中,如果使用普通集合往往会造成数据错误,甚至造成程序崩溃.Java为多线程专门提供了特有的 ...

  2. 40个Java多线程问题总结

    前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多.越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的.这篇文章主要是对多线程的问题进行 ...

  3. Java多线程基础知识篇

    这篇是Java多线程基本用法的一个总结. 本篇文章会从一下几个方面来说明Java多线程的基本用法: 如何使用多线程 如何得到多线程的一些信息 如何停止线程 如何暂停线程 线程的一些其他用法 所有的代码 ...

  4. Java多线程系列--“JUC锁”03之 公平锁(一)

    概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...

  5. Java多线程系列--“JUC锁”04之 公平锁(二)

    概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...

  6. Java多线程--让主线程等待子线程执行完毕

    使用Java多线程编程时经常遇到主线程需要等待子线程执行完成以后才能继续执行,那么接下来介绍一种简单的方式使主线程等待. java.util.concurrent.CountDownLatch 使用c ...

  7. Java多线程 2 线程的生命周期和状态控制

    一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就 ...

  8. java 多线程 1 线程 进程

    Java多线程(一).多线程的基本概念和使用 2012-09-10 16:06 5108人阅读 评论(0) 收藏 举报  分类: javaSE综合知识点(14)  版权声明:本文为博主原创文章,未经博 ...

  9. 一起阅读《Java多线程编程核心技术》

    目录 第一章 Java多线程技能 (待续...)

随机推荐

  1. python语言学习---4

    第五天 1.任意个参数函数怎么敲? 只需定义一个可变参数即可:可变参数名字前要加 * ,可以传入0个或多个参数. #内部解释器原理:Python解释器会把传入的一组参数组装成一个tuple(不可变)传 ...

  2. 章节二、1-java概述-数据类型

    一.数据类型 1.基本数据类型 a.数值型 1.整数:byte(1个字节=8位) min:-128 max:127 default:0 .short(2个字节=16位) min:-32768 max: ...

  3. (转载)SPARKR,对RDD操作的介绍

    原以为,用sparkR不能做map操作, 搜了搜发现可以. lapply等同于map, 但是不能操作spark RDD. spark2.0以后, sparkR增加了 dapply, dapplycol ...

  4. 2019年Web前端最新导航(常见前端框架、前端大牛)

    本文最初发表于博客园,并在GitHub上持续更新前端的系列文章.欢迎在GitHub上关注我,一起入门和进阶前端. 前言 本文列出了很多与前端有关的常见网站.博客.工具等,整体来看比较权威.有些东西已经 ...

  5. [20170612]FOR ALL COLUMNS SIZE repeat(12c).txt

    [20170612]FOR ALL COLUMNS SIZE repeat(12c).txt --//昨天看了https://jonathanlewis.wordpress.com/2017/06/0 ...

  6. 数据库之mysql篇(4)—— navicat操作mysql

    navicat 1.简介: navicat是一个软件,旗下针对不同数据库有不同的软件版本,支持以下数据库,还是挺厉害的: 这里我采用navicat for mysql版本.实现图形化的操作mysql, ...

  7. [转]JIRA 7.2.6与Confluence 6.0.3的安装与配置之MS SQL Server版

    相关软件版本信息 说明:下方软件可以点击链接,通过百度云盘进行下载. 操作系统:Windows 10(密码:foht)或者Windows Server 2012(密码:lsad): 数据库:SQL S ...

  8. Unity Shader 基础(2) Image Effect

    Unity中 Image Effect 是Post Processing的一种方,Unity自身也提供很多Effect效果供使用.Image Effect的使用官方文档做了很多介绍,这里重点Post ...

  9. MATLAB求解二重积分案例

    凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ 定积分解决的是一维连续量求和的问题,而解决多维连续量的求和问题就要用到重积分了.重积分是建立在定积分的基础上的 ...

  10. Teradata全面转型

    大数据时代 Teradata全面转型 [关键点]:数据分析相关技术和方案==>帮助企业实现数据价值变现 1.所有企业达成共识 数据已经成为企业的资产,甚至是核心资产. 2.Teradata转型 ...