在多线程编程过程中,遇到这样的情况,主线程需要等待多个子线程的处理结果,才能继续运行下去。个人给这样的子线程任务取了个名字叫并行任务。对于这种任务,每次去编写代码加锁控制时序,觉得太麻烦,正好朋友提到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. VMWare上的ubuntu系统安装VMWare Tools(图文)

    一.启动vm,点击这里安装 二.将文件复制到桌面 三.在终端用命令,把文件解压到桌面 四.执行安装命令  sudo ./vmware-install.pl 五.效果图

  2. How do I install Daydream on my phone?

    Google's philosophy with their newest VR platform is simple. In order to offer the best possible exp ...

  3. Android SurfaceView实现跟随手指移动的光标

    实例 public class DragSurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable{ priv ...

  4. 微信小程序中如何获取for循环的item相关值到JS页面的问题

    今天小程序开发过程中,遇到了这个棘手的问题.由于我没有前端基础,只是知道一点儿基本的HTML标签,所以卡了好久,特此分享,望后来的你,可以有所收获. measure  step 1  *.WXML: ...

  5. spring学习总结——装配Bean学习一(自动装配)

    一.Spring配置的可选方案 Spring容器负责创建应用程序中的bean并通过DI来协调这些对象之间的关系.但是,作为开发人员,你需要告诉Spring要创建哪些bean并且如何将其装配在一起.当描 ...

  6. 《React与Redux开发实例精解》读书笔记

    第五章 JSX语法 class属性改为className for属性改为htmlFor jsx中javascript表达式必须要有返回值,使用三元操作符 所有的标签必须闭合 input img等 re ...

  7. 自动化测试基础篇--Selenium简单的163邮箱登录实例

    摘自https://www.cnblogs.com/sanzangTst/p/7472556.html 前面几篇内容一直讲解Selenium Python的基本使用方法.学习了什么是selenium: ...

  8. UGUI ScrollRect 滑动

    运行环境 Unity3D 5.3.7 p4 在我之前的博客中,写过一些Unity4.6的UGUI,现这篇是基于Unity 5.3的 推荐结构 推荐使用三层来组织,如下所示: ScrollRect :S ...

  9. cURL模拟POST提交数据

    首先,是这个代码: <?php //curl模拟post提交数据$url = "http://127.0.0.1/immoc/output.php";$post_data = ...

  10. 【PAT】B1004 成绩排名

    简单题 循环输入放入结构体数组,一个sort搞定 #include <stdio.h> #include<algorithm> using namespace std; str ...