前言

线程的创建是需要JVM和OS(操作系统)相互配合的,一次的创建要花费许多的资源。

1.首先,JVM要为该线程分配堆栈和初始化大量内存块,栈内存至少是1MB。

2.其次便是要进行系统的调用,在OS中创建和注册本地的线程。

在Java的高并发场景下频繁的创建和销毁线程,一方面是内存块的频繁分配和回收,另一方面是操作系统频繁注册线程和销毁,内存资源利用率不高的同时,也增加了时间的成本,这是非常低效的。我们要做的是在线程执行完用户代码逻辑块后,保存该线程,等待下一次用户代码逻辑块来到时,继续去运用该线程去完成这个任务,这样不仅减少了线程频繁的创建和销毁,同时提高了性能。具体的实现便是线程池技术。

JUC线程池架构

线程池技术主要来自于java.util.concurrent包(俗称JUC),该包是JDK1.5以后引进来的,主要是完成高并发,多线程的一个工具包。

线程池主要解决了线程的调度,维护,创建等问题,它在提高了线程的利用率的同时还提高了性能。

在JUC中,有关线程池的类和接口大致如图下所示:

接下来让我们一个一个解析每个接口和类吧。

Exector接口

我们从源码来看Exector

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

我们可以看到Exector只有一个接口方法便是execute()方法,该方法是定义是线程在未来的某个时间执行给定的目标执行任务,也就是说线程池中的线程执行目标任务的方法。

ExecutorService接口

该接口继承了Executor因此它也继承了execute()方法,同时它本身也扩展了一些重要的接口方法,我们通过源码看一下几个比较常用的方法

public interface ExecutorService extends Executor {

    void shutdown();

    List<Runnable> shutdownNow();

    boolean isShutdown();

    boolean isTerminated();

    <T> Future<T> submit(Callable<T> task);

    <T> Future<T> submit(Runnable task, T result);

    Future<?> submit(Runnable task);
}
  • shutdown()接口方法的定义是关闭线程池,它与shutdownNow()方法不同的点在于,它不会中止正在执行的线程,它也会把未完成的目标任务完成了,此时线程池的状态未SHUTDOWN,执行回调函数后关闭线程池。
  • shutdownNow()接口方法的定义也是关闭线程池,它与shutdown()方法不同的点在于,它会中止正在执行的线程,清空已提交但未执行的目标任务,返回已完成的目标任务,同时线程池的状态为STOP,执行回调函数后关闭线程池。
  • isShutdown()接口方法的定义是判断当前的线程池状态是否是SHUTDOWN状态,是返回true,不是返回false。
  • isTerminated()接口方法的定义是判断当前线程池状态是否是TERMINATED状态,也就是判断当前线程池是否已经关闭,不是返回flase,是返回true。
  • submit()接口方法的定义与execute()方法类似,也是提交目标任务给线程池,线程池中的线程在适合的时机去执行该目标任务,它与execute()方法不同的点在两个:一方面是submit()方法的形参可以有是Callable类型的,也可以是Runnable类型的,而execute()方法仅能接收Runnable类型的,另一方面是submit()方法的返回值类型是Future,这意味着,我们可以获取到目标任务的执行结果,以及任务的是否执行、是否取消等情况,而execute()方法的返回值是void类型,这也表示我们获取不到目标任务的执行情况等信息。

AbstractExecutorService抽象类

正如第一张图显示的:AbstractExecutorService抽象类继承了ExecutorService接口,这意味着AbstractExecutorService抽象类拥有着父类ExecutorService接口的所有接口方法,同时因为ExecutorService接口又继承了Executor接口,因此也拥有Executor接口的接口方法。

不仅如此AbstractExecutorService抽象类实现了除shutdown()、shutdownNow()、execute()、isShutdown()、isTerminated()以外的方法,这里我们主要查看一下submit()方法的实现,同时也可以加深我们对execute()和submit()的关系与区别。

    public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}

我们来解读一下源码:

  1. 首先判断传进来的Runnable类型的对象是否为空,如果为空的话便抛出一个空指针异常。
  2. 若不为空,则将目前的Runnable类型对象传入newTaskFor方法,我们走进newTaskFor方法可以发现,其将Runnable类型对象修饰成了一个FutureTask类型的对象,FutureTask是实现了RunnableFuture接口的实现类,因此可以将其赋予给ftask。
  3. 随后,调用了execute方法,将ftask作为目标任务,传入线程池,等待线程池调度线程执行这个任务,最后再返回ftask,便于调用线程监控目标任务的执行情况和执行结果。

从源码分析我们可以得知,submit()方法本质上还是调用了executor()方法,只不过将Runnable类型的对象修饰成了FutureTask类型,让其拥有监控执行任务的能力而已。

有关Callable接口和FutureTask实现类以及RunnableFuture接口的详细信息可以查阅笔者另一篇随笔: https://www.cnblogs.com/qzlzzz/p

ThreadPoolExecutor线程池实现类

ThreadPoolExecutor继承了AbstractExecutorService抽象类,因此也就拥有了AbstractExecutorService抽象类继承的实现了的接口方法和AbstractExecutorService抽象类所继承的未实现的接口方法。

在此前提下,ThreadPoolExecutor不仅实现了AbstractExecutorService抽象类未实现的接口方法,同时其内部真正的实现了一个线程池,且实现了线程池的调度,管理,维护,空闲线程的存活时间,默认的线程工厂,和阻塞队列,核心线程数,最大线程数,淘汰策略等功能。我们也可得知ThreadPoolExecutor是线程池技术的核心、重要类。"由于本随笔仅说JUC的线程池架构,因此不多描述线程池的实现等其核心功能"

这里我们着眼于ThreadPoolExecutor对execute()方法的实现,首先我们来看其源码:

    public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}

其实在源码中已经有很详细的注释解析了,甚至不追及都可以懂这段代码的作用,我想这就是好的程序员的一种体现,笔者在追寻源码的路上时候也慢慢体会到尤雨溪大佬为什么那么强调:想要水平提升,必须有好的英语。

接着我们来大致说一下源码:

1. 首先会判断传入进来的Runnable类型的对象是否为空,如果为空则抛出一个空指针异常。

2. 随后获取ctl的值,ctl是原子类,在定义时它的初始值是 -536870912,获取到值后赋给c变量,c变量传入workerCountOf()方法,在方法的内部进行了或运算,以此来获取线程池的线程数,如果线程池的线程数比定义线程池时所设置的核心线程数要少的话,不管线程池里的线程是否空闲,都会新建一个线程。

3. 判断为true的话,进入到嵌套if()中的addWorker()方法。

这里我们再来探寻一下addWorker()方法的源码:

    private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (int c = ctl.get();;) {
// Check if queue empty only if necessary.
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false; for (;;) {
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
} boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get(); if (isRunning(c) ||
(runStateLessThan(c, STOP) && firstTask == null)) {
if (t.getState() != Thread.State.NEW)
throw new IllegalThreadStateException();
workers.add(w);
workerAdded = true;
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}

在addWorker()方法里面我们可以看到充斥着大量线程池在SHUTDOWN和STOP状态时,线程池该怎样去运作,当线程池中的线程数达到核心线程数,线程池又如何去做,以及如何选取空余的线程去执行目标任务或者在阻塞队列中的目标任务等调度,创建功能。

在如此长的一段代码中我们关注这几行:

            //第一段代码
w = new Worker(firstTask);
final Thread t = w.thread;

以及

                //第二段代码
if (workerAdded) {
t.start();
workerStarted = true;
}

首先是第一段代码,我们可以看到它将目标任务Runnable类型的对象修饰成了Worker类型,我们翻看一下Worker类:

    private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable{
//省略其他代码 Runnable firstTask; Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
} public void run() {
runWorker(this);
} //省略其他代码
}

从Worker的构造方法中我们可以知道其将目标任务,传给了自己的实例属性,同时由于自己本身是Runnable的实现类,因此可以以自己本身作为参数传入到线程工厂的构造线程方法中,而自己本身实现的run()方法中又调用了runWorker()方法,runWorker()方法的参数又是当前Worker的实例本身,如果读者有意深入的话,会发现runWorker()方法体中有一段是task.run()去执行目标任务,其余的代码则是回调函数的调用。

也就是说线程工厂创建的线程,如果启动该线程去执行的话,是执行Worker类中的run()方法,也就会去执行run()方法中的runWorker()方法。

然后我们继续来看第一段代码,其使用句柄w获取到thread,赋予给了Thread类型的t变量。第一段代码结束,再到第二段代码中使用了t.start()来启动这个线程去执行目标任务,再将这个任务的工作状态设为ture。

至此,两段代码探讨结束。

4. 最后回到execute()方法中,继续走下去便是一些线程池拒绝策略的判断,在这里就不过多叙述了。

ScheduledExecutorSerive接口

从关系图可以得知ScheduledExecutorService接口继承了ExecutorService接口,这说明ScheduledExecutorService接口拥有着ExecutorService接口的接口方法,同时除了ExecutorService的提交、执行、判断线程池状态等的接口方法之外,ScheduledExecutorService接口还拓展了一些接口方法。

这里我们从接口定义中来解读ScheduledExecutorService究竟增加了那些功能。

public interface ScheduledExecutorService extends ExecutorService {

    public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit); public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay, TimeUnit unit); public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit); public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}
  • 可以看出schedule()有两种重载方法,区别在于第一种方法接收的形参是Runnable类型的,第二种方法接收的形参是Callable类型的。其作用都是前一次执行结束到下一次执行开始的时间是delay,单位是unit。
  • scheduleAtFixedRate()接口方法的定义是首次执行目标任务的时间延迟initialDelay,两个目标任务开始执行最小间隔时间是delay,其单位都是unit。
  • scheduleWithFixedDelay()接口方法的定义与schedule()方法类似,只不过是首次执行的时间延迟initialDelay,单位是unit。

注意上面的方法都是周期的,也就是说会周期地执行目标任务。

ScheduledThreadPoolExecutor线程池实现类

ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,同时实现了ScheduledExecutorSerive接口,这意味着ScheduledThreadPoolExecuotr不仅拥有了ThreadPoolExecutor实现的线程池,同ScheduledExecutorService接口继承的接口方法也无需其实现,因为ThreadPoolExecutor和AbstractExecutorService已经帮其实现了。在此基础上,ScheduledThreadPoolExecutor实现了ScheduledExecutorService接口拓展的方法,这使得ScheduledThreadPoolExecutor成为一个执行"延时"和"周期性"任务的可调度线程池。

至此,JUC线程池架构也逐渐清晰了起来,Exector接口定义了最重要的execute方法,ExecutorService 则拓展了提交和执行的方法,也扩展了监控线程池的方法、AbstractExecutorService抽象类则负责实现了ExecutorService 接口拓展的方法,因为ThreadPoolExecutor类内部实现了线程池,所以监控线程池的方法和execute方法等重要的方法自然也交给了其实现。最后的ScheduledThreadPoolExecuto类其实也是在整个完整的线程池技术上,拓展了线程池的一些功能。

结尾

一定要吃早餐,学习的过程需注意自己的身体才行。

细说JUC的线程池架构的更多相关文章

  1. java多线程系类:JUC线程池:01之线程池架构

    概要 前面分别介绍了"Java多线程基础"."JUC原子类"和"JUC锁".本章介绍JUC的最后一部分的内容--线程池.内容包括:线程池架构 ...

  2. Java多线程系列--“JUC线程池”01之 线程池架构

    概要 前面分别介绍了"Java多线程基础"."JUC原子类"和"JUC锁".本章介绍JUC的最后一部分的内容——线程池.内容包括:线程池架构 ...

  3. Java - "JUC线程池" 架构

    Java多线程系列--“JUC线程池”01之 线程池架构 概要 前面分别介绍了"Java多线程基础"."JUC原子类"和"JUC锁".本章介 ...

  4. 001-多线程-JUC线程池-线程池架构-Executor、ExecutorService、ThreadPoolExecutor、Executors

    一.概述 1.1.线程池架构图 1. Executor 它是"执行者"接口,它是来执行任务的.准确的说,Executor提供了execute()接口来执行已提交的 Runnable ...

  5. JUC自定义线程池练习

    JUC自定义线程池练习 首先上面该线程池的大致流程 自定义阻塞队列 首先定义一个双向的队列和锁一定两个等待的condition 本类用lock来控制多线程下的流程执行 take和push方法就是死等, ...

  6. JUC线程池之 线程池架构

    线程池的架构图如下: Executor 它是"执行者"接口,它是来执行任务的.准确的说,Executor提供了execute()接口来执行已提交的 Runnable 任务的对象.E ...

  7. JAVA 线程池架构浅析

    经历了Java内存模型.JUC基础之AQS.CAS.Lock.并发工具类.并发容器.阻塞队列.atomic类后,我们开始JUC的最后一部分:线程池.在这个部分你将了解到下面几个部分: 线程池的基础架构 ...

  8. Java 线程池架构原理和源码解析(ThreadPoolExecutor)

    在前面介绍JUC的文章中,提到了关于线程池Execotors的创建介绍,在文章:<java之JUC系列-外部Tools>中第一部分有详细的说明,请参阅: 文章中其实说明了外部的使用方式,但 ...

  9. 【转】JUC下面线程池介绍

    介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行一个异步任务你还只是如下new T ...

随机推荐

  1. datetime和timestamp的区别

    时间日期数据类型总概况 MySQL中有多种表示时间日期的数据类型,主要有YEAR.TIME.DATE.DATETIME.TIMESTAMP等.每一种数据类型都有存储的时间日期格式.以及取值范围,因此在 ...

  2. Springboot中配置druid

    pom文件信息: <!--引入druid数据源--> <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> ...

  3. 映射Map、队列Queue、优先级队列PriorityQueue

    映射Map 将对象映射到其他对象的能力是解决编程问题的有效方法.例如,考虑一个程序,它被用来检查 Java 的 Random 类的随机性.理想情况下, Random 会产生完美的数字分布,但为了测试这 ...

  4. 交互式查询⼯具Impala

    Impala是什么: Impala是Cloudera提供的⼀款开源的针对HDFS和HBASE中的PB级别数据进⾏交互式实时查询(Impala 速度快),Impala是参照⾕歌的新三篇论⽂当中的Drem ...

  5. 前端 | 页面触底自动加载 Vue 组件

    不管是 web 端还是移动端,信息流都是现在很流行的信息展示方式.信息流经常搭配自动加载一起使用以获得更好的使用体验. 最近在使用 Vue 开发过程中也遇到了首页信息流自动加载的需求.大致了解了一下几 ...

  6. rabbitMq接收实体

  7. golang——rune

    byte 等同于int8,常用来处理ascii字符 rune等同于int32,常用来处理unicode或utf-8字符//可以处理中文

  8. 利用元数据提高 SQLFlow 血缘分析结果准确率

    利用元数据提高 SQLFlow 血缘分析结果准确率 一.SQLFlow--数据治理专家的一把利器 数据血缘属于数据治理中的一个概念,是在数据溯源的过程中找到相关数据之间的联系,它是一个逻辑概念.数据治 ...

  9. MySQL 慢 SQL & 优化方案

    1. 慢 SQL 的危害 2. 数据库架构 & SQL 执行过程 3. 存储引擎和索引的那些事儿 3.1 存储引擎 3.2 索引 4. 慢 SQL 解决之道 4.1 优化分析流程 4.2 执行 ...

  10. Excel 快速跳转到工作表

    新建 vba 模块 Sub GotoSheet() tname = InputBox("input table name") If StrPtr(tname) = 0 Then E ...