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. vue打包后找不到资源路径问题

    问题描述: 使用webpack打包vue项目后,前后端联调无法找到资源 解决方案: 一. 改为相对路径,去除axios中地址的第一个“/” onProxyReq: function (proxyReq ...

  2. datatable 写入excel 2007

    1 添加引用: NPOI NPOI.OOXML 2 private static void GenerateFile(DataTable dt) { DataSet ds = new DataSet( ...

  3. 6.SpringMVC 配置式开发-处理器

    处理器除了实现Controller 接口外,还可以继承自一些其他的类,来完成一些特殊的功能 1.继承自AbstractController类 若处理器继承自AbstractController类,那么 ...

  4. [react] - 循环请求 redux-saga

    //根据uuid 获取 apt报告信息 *getNotesByUid({ payload, callback }, { call, put }) { // payload 是个数组, 并发执行,参考r ...

  5. 【1】Git基础

    一.Git概念 1.1.Git定义   Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目.Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发 ...

  6. jumpserver跳板机docker安装小小趟坑

    最近日常运维的时候发现每次登陆服务器都要打开终端目录连接对应的服务器,闲暇的时候还好,运维任务很重的时候才发现这样的玩法很傻,浪费时间且一点儿都跟不上潮流,然后打开githup开始搞起来.docker ...

  7. Dart运算符与语句格式

    算术运算符 + 加 - 减 * 乘 / 除 ~/ 取整 % 取余 关系运算符 == 是否等于 != 是否不等于 > 是否大于 < 是否小于 >= 是否大于等于 <= 是否小于等 ...

  8. 去掉行尾的^M

    1. 处理掉行尾的^M 在windos下进行linux内核驱动编写,调试成功后需要集成到内核代码中去,所以会通过虚拟机共享文件夹拷贝到内核对应目录,这时候看源码文件还是没有异常的. 当对该文件进行回车 ...

  9. GOLANG文件拷贝

    GOLANG文件拷贝 在Golang中,使用系统自带函数io.Copy() 如: srcFile := "C:/Users/Wisdom/Desktop/Wisdompic.png" ...

  10. 在SqlServer和Oralce中创建索引

    给表名A的字段A增加索引 SqlServer: if exists (select 1 from sysobjects where name='表名A' and type='u')and exists ...