1.ThreadPoolExcuter运行实例

  首先我们先看如何新建一个ThreadPoolExecutor去运行线程。然后深入到源码中去看ThreadPoolExecutor里面使如何运作的。

public class Test {
public static void main(String[] args){
/**
* 新建一个线程池
* corePoolSize:2
* maximumPoolSize:10
* keepAliveTime:20
* unit:TimeUnit.SECONDS(秒)
* workQueue:new ArrayBlockingQueue(10)
* threadFactory:默认
* RejectedExecutionHandler默认
*/
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,10,20, TimeUnit.SECONDS,new ArrayBlockingQueue(10));
//用execute添加一个线程
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}

2.ThreadPoolExecute.execute方法

  可以发现,其实使用线程池ThreadPoolExcuter 就是使用这个方法,然后我们看这个方法具体的代码。

    /**
* 在后面执行给定任务。任务在一个新的线程中或一个存在的worker的线程池中执行。
* 如果一个线程不能提交到excution,可能是因为这个excutor已经shundown或者因为其容量已经是最大,
* 此时任务将会被RejectedExecutionHandler处理
*
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
* 有以下3个步骤
*
* 1.如果少于corePoolSize的线程在运行,那么试着启动一个新线程,其中用给定指令作为first task。
* 这会调用addWorker去原子性得检查runState和workerCoune,因此可以防止错误报警,在错误报警不应该时通过返回false来添加线程
* 2.如果任务被成功排队,我们任然应该第二次检查是否添加一个新线程(因为可能存在在最后一次检查后挂掉的情况)
* 或者在进入这个方法期间线程池shutdown。所以我们再次检查状态,如果已关闭和有必要则退出队列,或者如果没有的话就开始一个新的线程。
* 3.如果我们无法将task入队,那么我们试图添加新线程。如果失败,那么知道我们shutdown或者是饱和的并拒绝task。
*/
int c = ctl.get();
//判断是否小于corePoolSize
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果pool在运行并且能提交到队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//这里进行再次检查,如果线程池没在运行并且成功删除task后,使用拒绝策略拒绝该task
if (! isRunning(recheck) && remove(command))
reject(command);
       //如果已经将task添加到队列中,而此时没有worker的话,那么新建一个worker。稍后这个空闲的worker就会自动去队列里面取任务来执行
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果无法提交那么按照拒绝策略拒绝task
else if (!addWorker(command, false))
reject(command);
}

  线程池的ThreadPoolExcuter基础和 ThreadPoolExcuter  Worker基本介绍在前一节已经有说过,可以点这里查看。可以看到这个方法的主要流程,其实都在注释里面说明了。可以发现里面主要调用了一个方法,addWorker()。 那么这个addWorker()又是什么东西呢。其实看方法名就很清楚了,就是新建一个Worker来执行你添加进来的task。

3.ThreadPoolExecute.addWorker()方法

    /**
* 检查当前的线程池状态和容量,是否可以让一个新的worker加入。如果可以,worker计数将会被调整,并且
* 如果可能,一个新的woker将会被创建和开始,将它当作第一个任务来运行。当线程池是stopped或shutdown状态时,
* 将返回false。当线程工厂创建失败而返回null或者抛出exception(比如典型的OOM)时,它也会返回fails。
* firstTask:新线程应该第一个运行的任务。当线程数少于corePoolSize时或是队列满时,workers使用一个初始化的
* first task来创建,用来进行分流。初始化空闲线程通常使用prestartCoreThread。
* core:为true,如果使用有界的corePoolSize,否则时maxPoolSize
* @return true if successful
* 添加Worker
*/
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
//状态值
int rs = runStateOf(c); // Check if queue empty only if necessary.
//关于状态值的检测
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false; for (;;) {
int wc = workerCountOf(c);
//关于容量的检测
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//用到了原子CAS方法比较,使用CAS增加worker计数器成功,才能进入下一步
if (compareAndIncrementWorkerCount(c))
break retry;
//重新获取ctl
c = ctl.get(); // Re-read ctl
//这里表示执行到这里的时候线程池的运行状态改变,需要重新跳到retry处执行
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
} boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//使用firstTask初始化Worker,first可能为null,那么则表示该worker为空闲
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 rs = runStateOf(ctl.get()); if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
//largestPoolSize为跟踪的目前最大线程数,因为之前已经做过判断,所以不会越界问题
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//workerAdded是在上面最后才设置的,确保这个变量能准确表示是否添加worker成功
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//再次检查
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}

  addWorker本事只是为线程池添加一个ThreadPoolExcuter  Worker,其本身所做的事情其实很简单,但难就难在要确保安全有效得添加一个ThreadPoolExcuter  Worker。为此addWorker()方法做了很多额外的工作。比如判断线程池的运行状态,当前Worker数量是否已经饱和等等。可以发现在这个方法,或者说整个ThreadPoolExecutor中,很多时候都是使用双重检查的方式来对线程池状态进行检查。其实这都是为了效率,最简单不过直接使用Synchronized或ReentranLock进行同步,但这样效率会低很多,所以在这里,只有在万不得已的情况下,才会使用悲观的ReentranLock。

  addWorker的最后直接调用了t.start,这里的t其实就是Worker它自己。接下来再看Worker是如何运行的。

4.ThreadPoolExecute.runWorker()方法

    /**
* 主要的Worker运行的循环。重复得获取从任务队列中取出task并执行它。
*/ final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
//取出firstTask,再将worker中的值-设置为null
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//不断循环取出线程运行
while (task != null || (task = getTask()) != null) {
w.lock();
//锁住线程
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
//如果当前线程是stop,那么将确认其为interrupted
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//调用钩子
beforeExecute(wt, task);
Throwable thrown = null;
try {
//运行
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}

  从源码中可以看出,一个ThreadPoolExcuter  Worker的工作其实就是不断使用getTask()方法从队列中获取新的任务来执行。值得一提的是,初始化参数里面的时间戳参数就是在这个方法里面运用的。在循环体中每次都使用锁以保证当前worker在运行task过程中不会被中断。同时运行时还会去调用两个内置的钩子:beforeExecute()和afterExecute(),这两个方法默认实现时空的。

  同时在运行的循环中每次都关注着ThreadPoolExecutor的运行状态,当线程池处于中断状态时,循环Worker的当前线程也会中断。

总结:说到这里就差不多把线程池运行task的流程说完了,当然其中忽略了很多的细节。但总而言之,ThreadPoolExecutor其实就是对worker进行管理,然后使用这些worker来执行用户提交的task。对用户提交的task的数量也进行一定的控制管理,比如超过一定数量时放入一个任务队列中等等。然后对线程池规定一些状态量,根据这些状态量对线程池进行控制。


如果觉得对你有帮助,不如花0.5元请作者吃颗糖,让他甜一下吧~~

ThreadPoolExecutor源码解析(二)的更多相关文章

  1. ThreadPoolExecutor系列<三、ThreadPoolExecutor 源码解析>

    本文系作者原创,转载请注明出处:http://www.cnblogs.com/further-further-further/p/7681826.html 在源码解析前,需要先理清线程池控制的运行状态 ...

  2. ThreadPoolExecutor系列三——ThreadPoolExecutor 源码解析

    ThreadPoolExecutor 源码解析 本文系作者原创,转载请注明出处:http://www.cnblogs.com/further-further-further/p/7681826.htm ...

  3. Mybatis源码解析(二) —— 加载 Configuration

    Mybatis源码解析(二) -- 加载 Configuration    正如上文所看到的 Configuration 对象保存了所有Mybatis的配置信息,也就是说mybatis-config. ...

  4. RxJava2源码解析(二)

    title: RxJava2源码解析(二) categories: 源码解析 tags: 源码解析 rxJava2 前言 本篇主要解析RxJava的线程切换的原理实现 subscribeOn 首先, ...

  5. Sentinel源码解析二(Slot总览)

    写在前面 本文继续来分析Sentinel的源码,上篇文章对Sentinel的调用过程做了深入分析,主要涉及到了两个概念:插槽链和Node节点.那么接下来我们就根据插槽链的调用关系来依次分析每个插槽(s ...

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

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

  7. 第十三章 ThreadPoolExecutor源码解析

    ThreadPoolExecutor使用方式.工作机理以及参数的详细介绍,请参照<第十二章 ThreadPoolExecutor使用与工作机理 > 1.源代码主要掌握两个部分 线程池的创建 ...

  8. Java并发包源码学习系列:线程池ThreadPoolExecutor源码解析

    目录 ThreadPoolExecutor概述 线程池解决的优点 线程池处理流程 创建线程池 重要常量及字段 线程池的五种状态及转换 ThreadPoolExecutor构造参数及参数意义 Work类 ...

  9. 【Java并发编程】21、线程池ThreadPoolExecutor源码解析

    一.前言 JUC这部分还有线程池这一块没有分析,需要抓紧时间分析,下面开始ThreadPoolExecutor,其是线程池的基础,分析完了这个类会简化之后的分析,线程池可以解决两个不同问题:由于减少了 ...

  10. iOS即时通讯之CocoaAsyncSocket源码解析二

    原文 前言 本文承接上文:iOS即时通讯之CocoaAsyncSocket源码解析一 上文我们提到了GCDAsyncSocket的初始化,以及最终connect之前的准备工作,包括一些错误检查:本机地 ...

随机推荐

  1. tar (child): bzip2: Cannot exec: No such file or directory报错

    [root@hejianlai-jenkins ~]# file android-ndk-r8-linux-x86.tar.bz2 android-ndk-r8-linux-x86.tar.bz2: ...

  2. mysql 开发基础系列2 整型数据类型

    Mysql 的数据类型 1. 对整数类型, Mysql 还支持类型名称后面的小括号内指定的显示宽度,例如int(5) 表示宽度小于5位时填满宽度,如果不显示指定宽度默认是int(11),一般配合zer ...

  3. 经典中的品味:第二章 C++基本的对象,类型和值(上)

    摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆木匠 希望转载,保留摘要,谢谢! 自律,是以积极而主动的态度,去解决人生的痛苦~ 上一章,我们大谈了Hel ...

  4. 从零开始学 Web 之 Vue.js(四)Vue的Ajax请求和跨域

    大家好,这里是「 从零开始学 Web 系列教程 」,并在下列地址同步更新...... github:https://github.com/Daotin/Web 微信公众号:Web前端之巅 博客园:ht ...

  5. 遇到的一些Jquery,js函数

     jQuery.extend()        jQuery.merge():函数用于合并两个数组内容到第一个数组. <script> $(function () { ,,], [,,] ...

  6. 08 训练Tensorflow下围棋

    这里介绍一下开源项目Mugo,它基于Tensorflow,可以使用sgf的棋谱训练围棋机器人,跟你下围棋,这里直接给出本人修改完善好的项目,只介绍一下用法. 链接:http://pan.baidu.c ...

  7. 03 使用Tensorflow做计算题

    我们使用Tensorflow,计算((a+b)*c)^2/a,然后求平方根.看代码: import tensorflow as tf # 输入储存容器 a = tf.placeholder(tf.fl ...

  8. MFC控件编程进度条编写

    MFC控件编程进度条编写 一丶进度条编程需要用到的方法 进度条MFC已经帮我们封装好类了. 叫做 CProgressCtrl  进度条编程也很简单. 封装的方法也就那个那几个. GetPos()  获 ...

  9. Win32知识之窗口绘制.窗口第一讲

    Win32知识之窗口本质 一丶摘要 在学习Win32的时候. 很多操作都是窗口进行操作的.那么今天就说一下窗口的本质是什么. 窗口的本质是不断绘制.是windows通过消息机制进行绘制的. 我们知道. ...

  10. Spring Boot (五)Spring Data JPA 操作 MySQL 8

    一.Spring Data JPA 介绍 JPA(Java Persistence API)Java持久化API,是 Java 持久化的标准规范,Hibernate是持久化规范的技术实现,而Sprin ...