一个完整的线程池应该具备以下要素:
-
任务队列:用于缓存提交的任务
-
线程数量管理功能:一个线程池必须能够很好的管理和控制线程数量,可以通过如下三个参数来实现,初始线程数量、最大线程数量、最低活跃线程数量或核心线程数量
-
任务拒绝策略:任务数量达到上限且任务队列已满,需要相应的拒绝策略通知任务提交者
-
线程工厂:用于个性化定制线程
-
QueueSize:任务队列的最大数量限制
-
Keepedalive时间:决定线程各个重要参数自动维护的时间间隔
直接使用Java自带线程池解决方案的原因:
1、自带线程池足够优秀,能满足大多数开发者的需求
2、随着JDK升级,这些工具会被不断优化或者添入更多特性
3、使用者越多,遇到问题时更加容易交流并获取答案
1、Executor&ExecutorService详解
Executor或ExecutorService直接被称为线程池,事实上,这两个接口只是定义了任务(Runnable/Callable)被提交执行的相关接口。Executor接口定义非常简单,只定义了一个简单任务提交方法,execute。ExecutorService接口继承了Executor接口,并提供了更多用于任务提交和管理的一些方法。
ThreadPoolExecutor是ExecutorService最为重要、最为常用的一个实现之一。我们常说的Java并发包线程池指的就是ThreadPoolExecutor。ThreadPoolExecutor会做如下支持:1、线程池中有一定数量的工作线程,并且线程数量以及任务数量会受到一定的控制和管理;2、任务的执行以异步的方式进行,即线程池提交任务的方法会立即返回;3、线程池会负责执行任务的统计信息。
ThreadPoolExecutor的构造函数齐全的参数有7个,分别是:1、corePoolSize,用于指定线程池中维护的核心线程数量;2、maximumPoolSize,用于设置线程池中允许的线程数量最大值;3、keepAliveTime,当线程池中线程数量超过核心线程数且处于空闲时,线程池将回收一部分线程让出系统资源,该参数用于设置超过核心线程数量的线程在多长时间内回收,与unit配合使用;4、TimeUnit,用于设定keepAliveTime的时间单位;5、workQueue,用于存放已提交至线程池但未被执行的任务;6、ThreadFactory,用于创建线程的工厂,可以自定义;7、RejectedExecutionHandler,当任务数量超过阻塞队列边界时,此时线程池会拒绝新增任务,此参数主要用于设置拒绝策略。TimeUnit、workQueue、ThreadFactory、RejectedExecutionHandler不能为null,corePoolSize可以设置为0,但不能小于0,并且corePoolSize不能大于线程池的最大线程数maximumPoolSize。线程池被构造成功后,其内部的运行线程并不会立即被创建,ThreadPoolExecutor的核心线程会采用一种Lazy的方式创建且运行。当线程池被创建且首次调用执行任务方法时才会创建并且运行线程。
在ThreadPoolExecutor的构造函数中提供了一个接口ThreadFactory,它用于定义线程池中的线程,可以通过该接口指定线程的命名规则、优先级、是否为daemon守护线程等信息。在ThreadPoolExecutor中提供了4种拒绝策略,同时允许自定义拒绝策略。ThreadPoolExecutor提供的拒绝策略分别是:1、DiscardPolicy,丢弃策略,任务直接被丢弃;2、AbortPolicy,中止策略,在无法受理任务时抛出RejectedExecutionException;3、DiscardOldestPolicy,丢弃队列中最老任务策略;4、CallerRunsPolicy,调用者线程执行策略,任务不会在线程池中执行,转由当前线程被阻塞地执行。
ScheduledExecutorService提供了任务被定时执行的特性,可以使用ScheduledThreadPoolExecutor来完成某些特殊的任务执行,如定时任务、周期性执行任务等。 ScheduledExecutorService中定义了4个与schedule相关的方法,用于执行定时任务:1、<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit),只执行一次,任务callable会在单位unit时间delay后被执行,且立即返回ScheduledFuture,在稍后可以通过Future获取异步任务的执行结果;2、Scheduled Future<?> schedule(Runnable command, long delay, TimeUnit unit),只执行一次,任务command会在单位unit时间delay后被执行,有返回ScheduledFuture,但是不包含任何执行结果,可以通过Future判断任务是否执行结束;3、ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit),任务command会根据固定的速率(period,时间单位为unit),在时间(initialDelay,时间单位为unit)后不断地被执行;4、ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit),与上一个方法类似,只不过以固定延迟单位时间的方式执行任务。
关闭线程池有几种不同形式的关闭:1、有序关闭(shutdown),该方法被执行后,新任务提交会被拒绝,但是工作线程正在执行的任务以及线程池阻塞队列中已经被提交的任务还会继续执行,当所有提交的任务都完成后,线程池的工作线程才会销毁,达到ExecutorService最终被关闭的目标。该方法是立即返回方法,不会阻塞等待所有任务处理结束及ExecutorService的最终关闭,如果要在线程池关闭后做下一步处理,可以配合awaitTermination,使当前线程进入阻塞,等待线程池关闭;2、立即关闭(shutdownNow),该方法会先将线程池状态修改为shutdown,然后未被执行的任务挂起并从任务队列中排干,其次尝试中断正在执行的任务处理的工作线程,最后返回未被执行的任务。新任务提交也会被拒绝;3、组合关闭(shutdown&shutdownNow),示例代码如下:
public void shutdownAndAwaitTermination(ExecutorService executor,long timeout,TimeUnit unit) {
executor.shutdown();
try {
if(!executor.awaitTermination(timeout, unit)) {
executor.shutdownNow();
if(!executor.awaitTermination(timeout, unit)) {
//executor is not terminated by normal
}
}
}catch(InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
FixedThreadPool,线程池核心线程数量和最大线程数量相等,因此线程池中工作线程数量始终是固定的。任务队列LinkedBlockingQueue(无边界),理论上提交至线程池的任务始终都会被执行,只有显式地执行线程池关闭方法才能关闭线程池。
SingleThreadPool,只有一个核心线程的线程池,但是Finalizable代理了该线程池,因此当线程池引用可被垃圾回收期回收时,线程池的shutdown会被执行。建议显式调用线程池关闭方法关闭线程池。
CachedThreadPool,根据需要创建新线程,会重用以前构造的可用线程。该线程池常用于提高执行量大、耗时较短、异步任务程序的运行性能。在该线程池中,如果有可用的线程,将会被直接重用,没有可用线程就会创建新的线程加入线程池。未被使用且空闲时间超过60s的线程将被终止并从线程池中移除,因此长时间空闲线程不会消耗任何资源。
ScheduledThreadPool,用于创建指定核心线程数量的ScheduledExecutorService。
WorkStealingPool,创建ForkJoinPool,线程池中工作线程会处理任务队列中与之对应的任务分片,如果某个线程处理的任务比较耗时,那么它负责的任务会被其他线程“窃取”执行。这个线程池中工作线程的数量等于CPU的核数。
2、Future和Callback
Future代表着一个异步任务在未来的执行结果,这个结果可以在最终某个时间节点通过get方法获得。Callable泛型接口允许任务执行结束后返回执行结果。Future通过捕获get方法异常的方式来获取异步任务执行的错误信息。线程池通过submit方法提交一个callable异步任务并返回Future形式的结果。线程池的submit方法也可以提交runnable接口形式的任务,但是返回值Future无任何内容,可以通过Future获取任务执行情况。
线程池可以通过调用invokeAny方法一次性提交一批任务,但是只关心第一个完成的任务和结果。invokeAny是一个阻塞方法,等到有一个任务完成就会退出阻塞。线程池可以通过调用invokeAll方法处理批量任务,关心所有异步任务的执行,调用后进入阻塞状态,直到所有异步任务执行结束并返回结果。
Future存在的不足之处有:1、无法被动接收异步任务的计算结果,任务完成后需要主动调用get获取结果;2、Future之间彼此孤立;3、Future没有很好的错误处理机制。
Google Guava的Future做了增强处理,可以通过注册回调函数而不再通过get方法等待最终计算结果。
3、ForkJoinPool详解
Fork/Join计算模型主要是充分利用多核CPU的并行运算能力,将一个复杂的任务拆分(fork)成若干个并行结算,然后将结果合并(join)。在JDK中,Fork/Join框架的实现为ForkJoinPool及ForkJoinTask等,使用不频繁,Parallel Stream底层的并行计算是由ForkJoinPool完成的。ForkJoinPool是Fork/Join Framework在java中的实现,也是该框架最核心的类之一。ForkJoinPool是ExecutorService的一个具体实现,用于管理工作线程,提供工具及获取有关线程池状态和性能等信息。ForkJoinTask是ForkJoinPool内部执行任务的基本类型,RecursiveAction和RecursiveTask都是其子类,RecursiveTask在子任务运行结束后会返回计算结果,RecursiveAction只专注于子任务运行本身,无返回结果。
天然适合递归分治算法。
4、CompletionService详解
CompletionService是一种将异步任务和结算结果Future解耦的设计方式。在CompletionService中,先提交任务,然后通过操作队列的方式来消费Future。CompletionService不具备异步任务的执行能力,异步任务是由其内部的ExecutorService完成,它只是对ExecutorService的一个封装,在其内部提供了阻塞队列用于Future的消费。异步任务完成后,运行结果Future才会被添加到CompletionService的阻塞队列中。
CompletionService的使用和正常ExecutorService没有什么差异,它是线程池的进一步封装,线程池关闭时,需要显式调用CompletionService内部线程池的关闭方法。
5、CompletableFuture详解
异步编程:程序运算与应用程序的主线程在不同的线程上完成,并且程序运算的线程能够向主线程通知其进度,以及成功失败与否的非阻塞式编码方式。CompletableFuture常用于异步编程。CompletableFuture实现自CompletionStage接口,该接口是同步或者异步任务完成的某个阶段,它可以是整个任务管道中的最后一个阶段,甚至是可以是管道中的某一个阶段,这样可以将多个CompletionStage链接在一起形成一个异步任务链,前置任务执行结束后会自动触发下一个阶段任务的执行。CompletableFuture还实现了Future接口。
CompletableFuture的方法中,大多数入参都是函数式接口,熟练理解这些函数式接口是灵活使用的前提和基础。CompletableFuture能够执行异步任务的基础是内部的ExecutorService,默认情况下为ForkJoinPool.commonPool(),也允许开发者显式的指定。ComplatableFuture中,实现Future的方法都是同步方法。ForkJoinPool.commonPool()是系统公用的线程池,不会受shutdown和shutdownNow影响,在退出JVM时关闭。因此,在使用CompletableFuture时,需要显式指定线程池,避免使用系统公用线程池。
CompletableFuture.supplyAsync(Supplier<T> supplier):异步执行supplier,结果以T的格式返回。
CompletableFuture.runAsync(Runnable runnable):异步执行任务。
CompletableFuture允许将执行的异步任务结果继续由下一个任务来执行,下一个任务还可以有下一级,以此类推,可以形成一个异步任务链或者任务pipeline。具体的任务链API如下:
thenApply:以同步的方式继续处理上一个异步任务的结果,并产生返回结果
thenApplyAsync:以异步的方式继续处理上一个异步任务的结果,并产生返回结果
thenAccept:以同步方式消费上一个异步任务的结果,无返回
thenAcceptAsync:以异步方式消费上一个异步任务的结果,无返回
thenRun:以同步方式执行Runnable任务
thenRunAsync:以异步方式执行Runnable任务
thenCompose/thenCompose:可以将多个Future的结果合并为一个Future的
allOf:多个独立的CompletableFuture同时并行执行
anyOf:一批独立的CompletableFuture只关注运行完成的第一个
CompletableFuture对于异常的处理要比Future合理很多,具体代码如下:
CompletableFuture.<String>supplyAsync(()->{
throw new RuntimeException();
}).handle((r,e)->{
if(e!=null) {
return "Error";
}else {
return r;
}
}).thenAccept(System.out::println);
CompletableFuture的方法很多,但是归纳起来,有如下几类:Future基本功能、执行异步任务、多CompletionStage的任务链、多Future的整合、多Future的并行计算等。
自定义线程池的示例代码:
public interface ThreadPool {
//提交任务到线程池
void execute(Runnable runnable);
//关闭线程池
void shutdown();
//获取线程池初始线程数
int getInitSize();
//获取线程池最大线程数
int getMaxSize();
//获取线程池核心线程数量
int getCoreSize();
//获取线程池缓存任务队列的大小
int getQueueSize();
//获取线程池活跃线程数量
int getActiveCount();
//查看线程池是否已经被关闭
boolean isShutdown();
}
//线程工厂
public interface ThreadFactory {
Thread createThread(Runnable runnable);
}
/**
* 任务队列,主要用于缓存提交到线程池中的任务
*/
public interface RunnableQueue {
//当有新任务进来时,首先offer到队列中
void offer(Runnable runnable);
//工作线程通过take获取Runnable
Runnable take() throws InterruptedException;
//获取任务队列中任务的数量
int size();
}
@FunctionalInterface
public interface DenyPolicy {
void reject(Runnable runnable,ThreadPool threadPool);
class DiscardDenyPolicy implements DenyPolicy{
@Override
public void reject(Runnable runnable, ThreadPool threadPool) {
// do nothing
}
}
class AbortDenyPolicy implements DenyPolicy{
@Override
public void reject(Runnable runnable, ThreadPool threadPool) {
throw new RunnableDenyException("The runnable "+runnable+" will be abort.");
}
}
class RunnerDenyPolicy implements DenyPolicy{
@Override
public void reject(Runnable runnable, ThreadPool threadPool) {
if(!threadPool.isShutdown()) {
runnable.run();
}
}
}
}
public class RunnableDenyException extends RuntimeException {
public RunnableDenyException(String message) {
super(message);
}
}
public class InteralTask implements Runnable {
private final RunnableQueue runnableQueue;
private volatile boolean running=true;
public InteralTask(RunnableQueue runnableQueue) {
this.runnableQueue=runnableQueue;
}
@Override
public void run() {
while(running&&!Thread.currentThread().isInterrupted()) {
try {
Runnable task=this.runnableQueue.take();
task.run();
}catch(InterruptedException e) {
running=false;
break;
}
}
}
public void stop() {
this.running=false;
}
}
import java.util.LinkedList;
public class LinkedRunnableQueue implements RunnableQueue {
private final int limit;
private final DenyPolicy denyPolicy;
private final LinkedList<Runnable> runnableList=new LinkedList<Runnable>();
private final ThreadPool threadPool;
public LinkedRunnableQueue(int limit,DenyPolicy denyPolicy,ThreadPool threadPool) {
this.limit=limit;
this.denyPolicy=denyPolicy;
this.threadPool=threadPool;
}
@Override
public void offer(Runnable runnable) {
synchronized(runnableList) {
if(runnableList.size()>=limit) {
denyPolicy.reject(runnable, threadPool);
}else {
runnableList.add(runnable);
runnableList.notifyAll();
}
}
}
@Override
public Runnable take() throws InterruptedException {
synchronized(runnableList) {
while(runnableList.isEmpty()) {
try {
runnableList.wait();
} catch (InterruptedException e) {
throw e;
}
}
return runnableList.removeFirst();
}
}
@Override
public int size() {
synchronized(runnableList) {
return runnableList.size();
}
}
}
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class BaseThreadPool extends Thread implements ThreadPool {
private final int initSize;
private final int maxSize;
private final int coreSize;
private int activeCount;
private final ThreadFactory threadFactory;
private final RunnableQueue runnableQueue;
private final long keepAliveTime;
private final TimeUnit timeUnit;
private volatile boolean isShutdown=false;
private final Queue<ThreadTask> threadQueue=new ArrayDeque<ThreadTask>();
private final static DenyPolicy DEFAULT_DENY_POLICY=new DenyPolicy.DiscardDenyPolicy();
private final static ThreadFactory DEFAULT_THREAD_FACTORY=new DefaultThreadFactory();
public BaseThreadPool(int initSize,int maxSize,int coreSize,int queueSize) {
this(initSize,maxSize,coreSize,DEFAULT_THREAD_FACTORY,queueSize,DEFAULT_DENY_POLICY,10,TimeUnit.SECONDS);
}
public BaseThreadPool(int initSize,int maxSize,int coreSize,ThreadFactory threadFactory,int queueSize,
DenyPolicy denyPolicy,long keepAliveTime,TimeUnit timeUnit) {
this.initSize=initSize;
this.maxSize=maxSize;
this.coreSize=coreSize;
this.threadFactory=threadFactory;
this.runnableQueue=new LinkedRunnableQueue(queueSize,denyPolicy,this);
this.keepAliveTime=keepAliveTime;
this.timeUnit=timeUnit;
this.init();
}
private void init() {
start();
for(int i=0;i<initSize;i++) {
newThread();
}
}
private void newThread() {
InteralTask internalTask=new InteralTask(runnableQueue);
Thread thread=this.threadFactory.createThread(internalTask);
ThreadTask threadTask=new ThreadTask(thread,internalTask);
threadQueue.offer(threadTask);
this.activeCount++;
thread.start();
}
private void removeThread() {
ThreadTask threadTask=threadQueue.remove();
threadTask.interalTask.stop();
this.activeCount--;
}
@Override
public void execute(Runnable runnable) {
if(this.isShutdown) {
throw new IllegalStateException("The thread pool is destroy.");
}
this.runnableQueue.offer(runnable);
}
@Override
public void run() {
while(!isShutdown&&!isInterrupted()) {
try {
timeUnit.sleep(keepAliveTime);
} catch (InterruptedException e) {
isShutdown=true;
break;
}
synchronized(this) {
if(isShutdown)break;
if(runnableQueue.size()>0&&activeCount<coreSize) {
for(int i=initSize;i<coreSize;i++) {
newThread();
}
continue;
}
if(runnableQueue.size()>0&&activeCount<maxSize) {
newThread();
}
if(runnableQueue.size()==0&&activeCount>coreSize) {
for(int i=coreSize;i<activeCount;i++) {
removeThread();
}
}
}
}
}
@Override
public void shutdown() {
synchronized(this) {
if(isShutdown)return;
isShutdown=true;
threadQueue.forEach(threadTask->{
threadTask.interalTask.stop();
threadTask.thread.interrupt();
});
this.interrupt();
}
}
@Override
public int getInitSize() {
if(this.isShutdown) {
throw new IllegalStateException("The thread pool is destroy.");
}
return this.initSize;
}
@Override
public int getMaxSize() {
if(this.isShutdown) {
throw new IllegalStateException("The thread pool is destroy.");
}
return this.maxSize;
}
@Override
public int getCoreSize() {
if(this.isShutdown) {
throw new IllegalStateException("The thread pool is destroy.");
}
return this.coreSize;
}
@Override
public int getQueueSize() {
if(this.isShutdown) {
throw new IllegalStateException("The thread pool is destroy.");
}
return this.runnableQueue.size();
}
@Override
public int getActiveCount() {
synchronized(this) {
return this.activeCount;
}
}
@Override
public boolean isShutdown() {
return this.isShutdown;
}
private static class ThreadTask{
Thread thread;
InteralTask interalTask;
public ThreadTask(Thread thread,InteralTask task) {
this.thread=thread;
this.interalTask=task;
}
}
private static class DefaultThreadFactory implements ThreadFactory{
private static final AtomicInteger GROUP_COUNTER=new AtomicInteger(1);
private static final ThreadGroup group=new ThreadGroup("MyThreadPool-"+GROUP_COUNTER.getAndDecrement());
private static final AtomicInteger COUNTER=new AtomicInteger(0);
@Override
public Thread createThread(Runnable runnable) {
return new Thread(group,runnable,"thread-pool-"+COUNTER.getAndDecrement());
}
}
}
import java.util.concurrent.TimeUnit;
public class BPLTest {
public static void main(String[] args) {
ThreadPool tp=new BaseThreadPool(2,4,6,1000);
for(int i=0;i<20;i++) {
tp.execute(()->{
try {
TimeUnit.SECONDS.sleep(10);
System.out.println(Thread.currentThread().getName()+" is running and done");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
}
for(;;) {
System.out.println("getActiveCount:"+tp.getActiveCount());
System.out.println("getQueueSize"+tp.getQueueSize());
System.out.println("getCoreSize"+tp.getCoreSize());
System.out.println("getMaxSize"+tp.getMaxSize());
System.out.println("=====================");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
; }
}
}