Executor技术UML图:
说明:
Executor: 最基础的父类,定义了一个接收Runnable对象的方法executor,其方法签名为executor(Runnable command)
ExecutorService: 比Executor使用更广泛的子类接口,其提供了生命周期管理的方法(运行,关闭,终止)。还提供可跟踪一个或多个异步任务执行状况返回Future的方法。
AbstractExecutorService: ExecutorService执行方法的默认实现
ScheduledExecutorService: 一个可定时调度任务的接口
ScheduledThreadPoolExecutor: ScheduledExecutorService的实现,一个可定时调度任务的线程池。
ThreadPoolExecutor: 线程池,可以通过调用Executors以下静态工厂方法来创建线程池并返回一个ExecutorService对象。
简单的例子:
ExecutorService executor = Executors.newSingleThreadExecutor();//使用Executors创建单个线程的executor
executor.execute(new Runnable(){//执行runable
@Override
public void run() {
System.out.println("executor test");
}
});
1. 线程池的相关
1) ThreadPoolExecutor构造函数的各个参数说明
构造函数各个参数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) //后两个参数为可选参数
参数说明:
corePoolSize:核心线程数,如果运行的线程少于corePoolSize,则创建新线程来执行新任务,即使线程池中的其他线程是空闲的
maximumPoolSize:最大线程数,可允许创建的线程数,corePoolSize和maximumPoolSize设置的边界自动调整池大小:
corePoolSize <运行的线程数< maximumPoolSize:仅当队列满时才创建新线程
corePoolSize=运行的线程数= maximumPoolSize:创建固定大小的线程池
keepAliveTime:如果线程数多于corePoolSize,则这些多余的线程的空闲时间超过keepAliveTime时将被终止
unit:keepAliveTime参数的时间单位
workQueue:保存任务的阻塞队列,与线程池的大小有关:
当运行的线程数少于corePoolSize时,在有新任务时直接创建新线程来执行任务而无需再进队列
当运行的线程数等于或多于corePoolSize,在有新任务添加时则选加入队列,不直接创建线程
当队列满时,在有新任务时就创建新线程
threadFactory:使用ThreadFactory创建新线程,默认使用defaultThreadFactory创建线程
handle:定义处理被拒绝任务的策略,默认使用ThreadPoolExecutor.AbortPolicy,任务被拒绝时将抛出RejectExecutorException
2) Executors提供一系列的静态工厂方法用于创建各种线程池的ExecutorService对象
newFixedThreadPool
:创建可重用且固定线程数的线程池,如果线程池中的所有线程都处于活动状态,此时再提交任务就在队列中等待,直到有可用线程;如果线程池中的某个线程由于异常而结束时,线程池就会再补充一条新线程。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
//使用一个基于FIFO排序的阻塞队列,在所有corePoolSize线程都忙时新任务将在队列中等待
new LinkedBlockingQueue());
}
newSingleThreadExecutor:创建一个单线程的Executor,如果该线程因为异常而结束就新建一条线程来继续执行后续的任务
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
//使用一个基于FIFO排序的阻塞队列,在所有corePoolSize线程都忙时新任务将在队列中等待
new LinkedBlockingQueue());
}
newScheduledThreadPool:创建一个可延迟执行或定期执行的线程池
例子:
public class HeartBeat {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);
Runnable task = new Runnable() {
public void run() {
System.out.println("HeartBeat.........................");
}
};
executor.scheduleAtFixedRate(task,5,3, TimeUnit.SECONDS); //5秒后第一次执行,之后每隔3秒执行一次
}
}
newCachedThreadPool:创建可缓存的线程池,如果线程池中的线程在60秒未被使用就将被移除,在执行新的任务时,当线程池中有之前创建的可用线程就重 用可用线程,否则就新建一条线程
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS, //使用同步队列,将任务直接提交给线程
new SynchronousQueue());}
例子:
public class ThreadPoolTest {
public static void main(String[] args) throws InterruptedException {
ExecutorService threadPool = Executors.newCachedThreadPool();//线程池里面的线程数会动态变化,并可在线程线被移除前重用
for (int i = 1; i <= 3; i ++) {
final int task = i; //10个任务
//TimeUnit.SECONDS.sleep(1);
threadPool.execute(new Runnable() { //接受一个Runnable实例
public void run() {
System.out.println("线程名字: " + Thread.currentThread().getName() + " 任务名为: "+task);
}
});
}
}
}
输出结果:
输出(为每个任务新建一条线程,共创建了3条线程)
线程名字: pool-1-thread-1 任务名为: 1
线程名字: pool-1-thread-2 任务名为: 2
线程名字: pool-1-thread-3 任务名为: 3
去掉第6行的注释其输出如下:(始终重复利用一条线程,因为newCachedThreadPool能重用可用线程)
线程名字: pool-1-thread-1 任务名为: 1
线程名字: pool-1-thread-1 任务名为: 2
线程名字: pool-1-thread-1 任务名为: 3
2. Executor的生命周期
ExecutorService提供了管理Executor生命周期的方法,ExecutorService的生命周期包括了:运行,关闭和终止的三种状态。
ExecutorService在初始化创建时处于运行状态
executor.shutdown(): 等待提交的任务执行完成并不再接收新任务时关闭。
executor.shutdownNow(): 强制终止所有运行中的任务,并不再允许提交新任务。
例子:
//创建10个固定线程池的executorService对象
ExecutorService executor = Executors.Executors.newFixedThreadPool(10);
for(int i = 0; i <= 300; i++) {
final int t = i;
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("task over" + t);
}
};
executor.execute(task);
}
executor.shutdown();//等待所有任务执行完时关闭。
3. 使用Callable, Future返回结果
ExecutorService的submit方法可以把Runnable或者Callable提交,返回Future对象,再通过此对象调用get方法获取执行任务结果,并且这个执行是阻塞的(只有当Runnable或者Callable里面的内容执行完成才执行)。
例子:
try {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future future = executorService.submit(new Callable() {
@Override
public String call() throws Exception {
return "MOBIN";
}
});
System.out.println("任务的执行结果:"+future.get());
executorService.shutdown();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
下面是使用Future的get方法来获取结果,来实现并行计算框架。
package com.wm.effectivejava.test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class ConcurrentCalculator {
private ExecutorService exec;
private int cpuCoreNumber;
private List> tasks = new ArrayList>();
// 内部类
class SumCalculator implements Callable {
private int[] numbers;
private int start;
private int end;
public SumCalculator(final int[] numbers, int start, int end) {
this.numbers = numbers;
this.start = start;
this.end = end;
}
public Long call() throws Exception {
Long sum = 0l;
for (int i = start; i < end; i++) {
sum += numbers[i];
}
return sum;
}
}
public ConcurrentCalculator() {
cpuCoreNumber = Runtime.getRuntime().availableProcessors();
exec = Executors.newFixedThreadPool(cpuCoreNumber);
}
public Long sum(final int[] numbers) {
// 根据CPU核心个数拆分任务,创建FutureTask并提交到Executor
for (int i = 0; i < cpuCoreNumber; i++) {
int increment = numbers.length / cpuCoreNumber + 1;
int start = increment * i;
int end = increment * i + increment;
if (end > numbers.length)
end = numbers.length;
SumCalculator subCalc = new SumCalculator(numbers, start, end);
FutureTask task = new FutureTask(subCalc);
tasks.add(task);
if (!exec.isShutdown()) {
exec.submit(task);
}
}
return getResult();
}
/**
* 迭代每个只任务,获得部分和,相加返回
*
* @return
*/
public Long getResult() {
Long result = 0l;
for (Future task : tasks) {
try {
// 如果计算未完成则阻塞
Long subSum = task.get();
result += subSum;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
return result;
}
public void close() {
exec.shutdown();
}
public static void main(String[] args){
int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 10, 11};
ConcurrentCalculator calc = new ConcurrentCalculator();
Long sum = calc.sum(numbers);
System.out.println(sum);
calc.close();
}
}
上例中,Future对象.get方法会阻塞,如果前面没有计算完会等计算完后,才能计算后面,会影响计算的性能。
CompletionService可以解决此问题,他是对Executor对象进行封装,并把任务完成的结果会自动收集起来,再使用take()获取已经完成的任务,并且按照完成任务顺序处理结果。也就是调用CompletionService的take方法是,会返回按完成顺序放回任务的结果,CompletionService内部维护了一个阻塞队列BlockingQueue,如果没有任务完成,take()方法也会阻塞。
修改刚才例子如下:
package com.wm.effectivejava.test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class ConcurrentCalculator {
private ExecutorService exec;
private int cpuCoreNumber;
//private List> tasks = new ArrayList>();
private CompletionService completionService;
// 内部类
class SumCalculator implements Callable {
private int[] numbers;
private int start;
private int end;
public SumCalculator(final int[] numbers, int start, int end) {
this.numbers = numbers;
this.start = start;
this.end = end;
}
public Long call() throws Exception {
Long sum = 0l;
for (int i = start; i < end; i++) {
sum += numbers[i];
}
return sum;
}
}
public ConcurrentCalculator() {
cpuCoreNumber = Runtime.getRuntime().availableProcessors();
exec = Executors.newFixedThreadPool(cpuCoreNumber);
completionService = new ExecutorCompletionService(exec);
}
public Long sum(final int[] numbers) {
// 根据CPU核心个数拆分任务,创建FutureTask并提交到Executor
for (int i = 0; i < cpuCoreNumber; i++) {
int increment = numbers.length / cpuCoreNumber + 1;
int start = increment * i;
int end = increment * i + increment;
if (end > numbers.length)
end = numbers.length;
SumCalculator subCalc = new SumCalculator(numbers, start, end);
//FutureTask task = new FutureTask(subCalc);
//tasks.add(task);
if (!exec.isShutdown()) {
completionService.submit(subCalc);
}
}
return getResult();
}
/**
* 迭代每个只任务,获得部分和,相加返回
*
* @return
*/
public Long getResult() {
Long result = 0l;
for (int i = 0; i < cpuCoreNumber; i++) {
try {
// 如果计算未完成则阻塞
Long subSum = completionService.take().get();
result += subSum;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
return result;
}
public void close() {
exec.shutdown();
}
public static void main(String[] args){
int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 10, 11};
ConcurrentCalculator calc = new ConcurrentCalculator();
Long sum = calc.sum(numbers);
System.out.println(sum);
calc.close();
}
}