ThreadPoolExecutor源码分析

简介

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。本文就从源码着手开始一步步了解ThreadPoolExecutor

类图

ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,通过观察每个构造器的源码,发现前面三个构造器都是调用的第四个构造器进行的初始化工作


public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {} public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {} public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {} public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}

构造函数中各个参数含义如下:

corePoolSize:核心线程池大小
默认情况下,在创建了线程池后,池中的线程数为0,当有任务来了之后就会创建线程去执行任务,当线程池中,线程的数量超过corePoolSize后,就会把新的任务放在缓存队列中 maxinumPoolSize:线程池所能容纳的最大线程数
超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效 keepAliveTime:非核心线程的闲置超时时间,超过这个时间就会被回收
当线程中没有可执行的任务时,最多保持多长时间会停止,默认情况下只有线程池中线程数量大于corePoolSize,该参数才会起作用。但是如果调用了allowCoreThreadTimeOut(boolean)方法,当线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0 unit:指定keepAliveTime的单位,如TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效 workQueue:阻塞队列
用来保存等待执行的任务 threadFactory:线程工厂
用来创建线程 handler:拒绝处理任务的策略
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

ThreadPoolExecutor重要方法

execute():
该方法是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行 submit():
在ExecutorService中声明的方法,在AbstractExecutorService有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果 shutdown():
关闭线程池,前提等线程池中正在执行的任务执行完毕再关闭 shutdownNow()
立即关闭线程池,会强制终止正在执行任务的线程

继承关系

从源码中看到ThreadPoolExecutor继承于AbstractExecutorService,我们看一下这个抽象类的源码发现AbstractExecutorService是一个抽象类,实现了ExecutorService接口

public abstract class AbstractExecutorService implements ExecutorService {

    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { };
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { };
public Future<?> submit(Runnable task) {};
public <T> Future<T> submit(Runnable task, T result) { };
public <T> Future<T> submit(Callable<T> task) { };
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
boolean timed, long nanos)
throws InterruptedException, ExecutionException, TimeoutException {
};
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException {
};
public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
};
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
};
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException {
};
}

接着看接口ExecutorService源码发现,它继承了接口Excetor

public interface ExecutorService extends Executor {

    void shutdown();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException; <T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}

再来看接口Executor,它只有一个方法,执行传入的任务

public interface Executor {
void execute(Runnable command);
}

总结:

Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,用来执行传进去的任务的;

ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;

AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;

然后ThreadPoolExecutor继承了类AbstractExecutorService。

ThreadPoolExecutor实现原理

1.线程池状态

//正在运行,接受新政务并且处理队列中的任务
private static final int RUNNING = -1 << COUNT_BITS;
//关闭状态,不接受新任务,但是处理队列中的任务
private static final int SHUTDOWN = 0 << COUNT_BITS;
//停止状态,既不接受新任务也不处理队列中的任务,并且终止正在执行任务的线程
private static final int STOP = 1 << COUNT_BITS;
//所有任务都终止了,workerCount为零,线程过渡到状态清理,将运行terminated() 终止方法
private static final int TIDYING = 2 << COUNT_BITS;
//完全终止状态
private static final int TERMINATED = 3 << COUNT_BITS;

状态转换条件:

RUNNING -> SHUTDOWN
On invocation of shutdown(), perhaps implicitly in finalize() (RUNNING or SHUTDOWN) -> STOP
On invocation of shutdownNow() SHUTDOWN -> TIDYING
When both queue and pool are empty STOP -> TIDYING
When pool is empty TIDYING -> TERMINATED
When the terminated() hook method has completed

2.任务的执行

public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//正在工作的线程数量小于线程池设定的数量,将会启动一个新线程执行任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果当前线程池处于RUNNING状态且将任务放入任务缓存队列成功,则继续进行判断
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//如果当前线程池不是UNNING状态并且从队列中移除任务失败,将拒绝接受该任务
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}

如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;

如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;

如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理

如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止

3.线程池中的线程初始化

默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。

在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:

prestartCoreThread():初始化一个核心线程;

prestartAllCoreThreads():初始化所有核心线程
public boolean prestartCoreThread() {
return workerCountOf(ctl.get()) < corePoolSize &&
addWorker(null, true);
} public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true))
++n;
return n;
}

注意上面传进去的参数是null,最后线程会阻塞在addWorker,等待任务队列中有任务

4.任务缓存队列及排队策略

在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务。

workQueue的类型为BlockingQueue,通常可以取下面三种类型:

1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;

2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;

3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

5.任务拒绝策略

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

6.线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

7.线程池容量的动态调整

ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),

setCorePoolSize:设置核心池大小
setMaximumPoolSize:设置线程池最大能创建的线程数目大小

线程池规则

线程池的线程执行规则跟任务队列有很大的关系。

下面都假设任务队列没有大小限制:

  • 如果线程数量<=核心线程数量,那么直接启动一个核心线程来执行任务,不会放入队列中。
  • 如果线程数量>核心线程数,但<=最大线程数,并且任务队列是LinkedBlockingDeque的时候,超过核心线程数量的任务会放在任务队列中排队。
  • 如果线程数量>核心线程数,但<=最大线程数,并且任务队列是SynchronousQueue的时候,线程池会创建新线程执行任务,这些任务也不会被放在任务队列中。这些线程属于非核心线程,在任务完成后,闲置时间达到了超时时间就会被清除。
  • 如果线程数量>核心线程数,并且>最大线程数,当任务队列是LinkedBlockingDeque,会将超过核心线程的任务放在任务队列中排队。也就是当任务队列是LinkedBlockingDeque并且没有大小限制时,线程池的最大线程数设置是无效的,他的线程数最多不会超过核心线程数。
  • 如果线程数量>核心线程数,并且>最大线程数,当任务队列是SynchronousQueue的时候,会因为线程池拒绝添加任务而抛出异常。

任务队列大小有限时

  • 当LinkedBlockingDeque塞满时,新增的任务会直接创建新线程来执行,当创建的线程数量超过最大线程数量时会抛异常。
  • SynchronousQueue没有数量限制。因为他根本不保持这些任务,而是直接交给线程池去执行。当任务数量超过最大线程数时会直接抛异常。

事例

public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,
10, 200, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5)); for(int i=0;i<10;i++){
MyTask myTask = new MyTask(i);
executor.execute(myTask);
System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
executor.getQueue().size()+",已执行完成的任务数目:"+executor.getCompletedTaskCount());
}
executor.shutdown();
}
static class MyTask implements Runnable {
private int taskNum; public MyTask(int num) {
this.taskNum = num;
} @Override
public void run() {
System.out.println("正在执行task "+taskNum);
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task "+taskNum+"执行完毕");
}
}
}

Java并发之ThreadPoolExecutor的更多相关文章

  1. Java并发之ThreadPoolExecutor 线程执行服务

    package com.thread.test.thread; import java.util.concurrent.ExecutorService; import java.util.concur ...

  2. Java并发之ThreadPoolExecutor源码解析(二)

    ThreadPoolExecutor ThreadPoolExecutor是ExecutorService的一种实现,可以用若干已经池化的线程执行被提交的任务.使用线程池可以帮助我们限定和整合程序资源 ...

  3. Java并发之ThreadPoolExecutor源码解析(三)

    Worker 先前,笔者讲解到ThreadPoolExecutor.addWorker(Runnable firstTask, boolean core),在这个方法中工作线程可能创建成功,也可能创建 ...

  4. java并发之Future与Callable使用

    java并发之Future与Callable使用 这篇文章需要大家知道线程.线程池的知识,尤其是线程池. 有的时候我们要获取线程的执行结果,这个时候就需要用到Callable.Future.Futur ...

  5. java线程池ThreadPoolExecutor使用简介

    一.简介线程池类为 java.util.concurrent.ThreadPoolExecutor,常用构造方法为:ThreadPoolExecutor(int corePoolSize, int m ...

  6. java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@1f303192 rejected from java.util.concurrent.ThreadPoolExecutor@11f7cc04[Terminated, pool size = 0, active threads

    java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@1f303192 rejec ...

  7. java 多线程(ThreadPoolExecutor (补充))

    import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; impo ...

  8. Java线程池--ThreadPoolExecutor

    一.线程池的处理流程 向线程池提交一个任务后,它的主要处理流程如下图所示: 一个线程从被提交(submit)到执行共经历以下流程: 线程池判断核心线程池里的线程是否都在执行任务,如果不是,则创建一个新 ...

  9. java.util.concurrent.ThreadPoolExecutor

    java.util.concurrent.ThreadPoolExecutor An ExecutorService that executes each submitted task using o ...

随机推荐

  1. Spring与Web框架(例如Spring MVC)漫谈——关于Spring对于多个Web框架的支持

    在看Spring MVC的官方文档时,最后一章是关于Spring对于其它Web框架的支持(如JSF,Apache Struts 2.x,Tapestry 5.x),当然Spring自己的MVC框架Sp ...

  2. hdu 1087最长上升子序列和问题

    看来大佬的dp思路,在这里就简单的总结下吧. 拿到一个问题,先得考虑是否适用dp算法. 1,找到最优解的结构,看其子问题是否也满足最优化(子问题最优化问题) 2,  看时候有子问题重叠 确定一个问题可 ...

  3. Scala学习二十一——隐式转换和隐式参数

    一.本章要点 隐式转换用于类型之间的转换 必须引入隐式转换,并确保它们可以以单个标识符的形式出现在当前作用域 隐式参数列表会要求指定类型的对象.它们可以从当前作用域中以单个标识符定义的隐式对象的获取, ...

  4. Advanced Installer 关于桌面的快捷方式。

    由于软件自动生成快捷方式,我发现桌面可以存在多个软件的快捷方式,因为快捷方式只要名字不同就可以存在多个,即使名字相同,只要备注不同,又可以存在多个. 那么由于软件自带生成快捷方式的功能,为了避免桌面出 ...

  5. 流媒体服务器搭建 ffmpeg + nginx

    第一部分: mkdir ~/working 切换到~/working目录下 cd ~/working 获取nginx源码: wget http://nginx.org/download/nginx-1 ...

  6. Redis缓存策略设计及常见问题

    Redis缓存设计及常见问题 缓存能够有效地加速应用的读写速度,同时也可以降低后端负载,对日常应用的开发至关重要.下面会介绍缓存使用技巧和设计方案,包含如下内容:缓存的收益和成本分析.缓存更新策略的选 ...

  7. RabbitMQ的特点与应用场景(二)

      1.RabbitMQ的主要特点 (1)可靠性:RabbitMQ可通过队列持久化,交换机持久化,消息持久化及ACK回应等机制保证可靠性 (2)支持多种语言与协议:RabbitMQ几乎支持所有的编程语 ...

  8. Oracle笔记(十五) 数据库备份

    数据库的备份操作是在整个项目运行中最重要的工作之一. 一.数据的导出和导入 数据的导出和导入是针对于一个用户的备份操作,可以按照如下的方式完成: 1.  数据的导出 在硬盘上建立一个文件夹:c:\ba ...

  9. springboot升级2.0 fastjson报错? 2.0以上应该怎么整合fastjson?

    SpringBoot2.0如何集成fastjson?在网上查了一堆资料,但是各文章的说法不一,有些还是错的,可能只是简单测试一下就认为ok了,最后有没生效都不知道.恰逢公司项目需要将JackSon换成 ...

  10. 06_Hive分桶机制及其作用

    1.Clustered By 对于每一个表(table)或者分区, Hive可以进一步组织成桶,也就是说桶是更为细粒度的数据范围划分. Hive也是针对某一列进行桶的组织.Hive采用对列值哈希,然后 ...