线程池ThreadPoolExecutor工作原理
前言
工作原理
如果使用过线程池,细心的同学肯定会注意到,new一个线程池,但是如果不往里面提交任何任务的话,main方法执行完之后程序会退出,但是如果向线程池中提交了任务的话,main方法执行完毕之后程序是不会自动退出的,是什么原理,或者说是什么原因导致任务提交到线程池之后任务执行完程序无法自动退出的呢?下面就让我们趴开线程池的源码,一探究竟。
我们直接从ThreadPoolExecutor的execute方法开始说起。线程提交到ThreadPoolExecutor执行分为三种情况,具体如下:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//1、当前线程池中的线程数小于corePoolSize
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//2、当前线程池中的线程数大于corePoolSize,直接将任务放入工作队列
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);
}
//3、如果入队失败,尝试启动新的线程,即此时工作队列已满,线程池中的线程数大于corePoolSize小于maxPoolSize
else if (!addWorker(command, false))
reject(command);//启动新的线程失败,执行拒绝策略
}
现在我们知道,将一个任务提交到ThreadPoolExecutor线程池执行分为三种情况,可以看到,三种情况下都有一个addWorker的动作,下面我们主要看看addWorker里面做了什么
private boolean addWorker(Runnable firstTask, boolean core) {
// 第一步,cas操作保证正确的增加任务数
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 如果当前线程池不处于RUNNING状态,则不能添加任务
if (rs >= SHUTDOWN && // 如果线程池状态rs >= SHUTDOWN,也就是非RUNNING状态,此时不接受新任务
! (rs == SHUTDOWN && //rs == SHUTDOWN ,此状态不接受新任务
firstTask == null &&
! workQueue.isEmpty())) // 工作队列不为空
return false;
for (;;) {
// 获取任务数量
int wc = workerCountOf(c);
//如果线程数 大于等于CAPACITY 添加任务失败
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))//尝试增加任务数量
break retry;
c = ctl.get(); // Re-read ctl
// 如果当前的运行状态不等于rs,说明状态已被改变,返回第一个for循环继续执行
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 第二步 创建一个Worker,包装当前的任务,并启动该work中创建的线程,用于执行当前当前提交过来的任务
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);//新建一个worker,同时从ThreadFactory中创建一个新的线程
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);//放入worker集合
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {//worker添加成功,启动任务
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
这里分为两步,首先使用cas操作保证成功增加workerCount,然后将创建一个worker,将worker添加人worker池,启动worker,返回任务添加成功
Worker是ThreadPoolExecutor线程池的内部类,主要作为用户提交任务的包装,它继承自AbstractQueuedSynchronizer类并实现了Runnable接口,它的run方法很简单,直接调用runWorker方法,runWorker如下:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
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
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);
}
}
可以看到,在runWorker中使用了一个while循环,使用getTask去获取任务, getTask如下:
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
getTask通过workQueue.take();方法获取任务,我们知道,blockingQueue的take方法是阻塞的,当队列为空时,会一直阻塞知道获取新的任务,到这里,我们便可以回答上面提到的问题了
重新梳理一遍,用户向ThreadPoolExecutor线程池中添加任务时,ThreadPoolExecutor会创建一个Worker,用来包装并执行用户任务,Worker的run方法中采用while循环,通过getTask方法不断的取出工作队列中的任务执行,当任务队列为空时,take方法阻塞了线程,导致任务执行线程一直不会退出,所以用户想ThreadPoolExecutor线程池中提交任务之后程序不会自动结束,就是这个原理。
线程池ThreadPoolExecutor工作原理的更多相关文章
- jdk线程池ThreadPoolExecutor工作原理解析(自己动手实现线程池)(一)
jdk线程池ThreadPoolExecutor工作原理解析(自己动手实现线程池)(一) 线程池介绍 在日常开发中经常会遇到需要使用其它线程将大量任务异步处理的场景(异步化以及提升系统的吞吐量),而在 ...
- 21.线程池ThreadPoolExecutor实现原理
1. 为什么要使用线程池 在实际使用中,线程是很占用系统资源的,如果对线程管理不善很容易导致系统问题.因此,在大多数并发框架中都会使用线程池来管理线程,使用线程池管理线程主要有如下好处: 降低资源消耗 ...
- 基于C++11实现线程池的工作原理
目录 基于C++11实现线程池的工作原理. 简介 线程池的组成 1.线程池管理器 2.工作线程 3.任务接口, 4.任务队列 线程池工作的四种情况. 1.主程序当前没有任务要执行,线程池中的任务队列为 ...
- Java8线程池ThreadPoolExecutor底层原理及其源码解析
小侃一下 日常开发中, 或许不会直接new线程或线程池, 但这些线程相关的基础或思想是非常重要的, 参考林迪效应; 就算没有直接用到, 可能间接也用到了类似的思想或原理, 例如tomcat, jett ...
- Java线程池的工作原理与实现
简单介绍 创建线程有两种方式:继承Thread或实现Runnable.Thread实现了Runnable接口,提供了一个空的run()方法,所以不论是继承Thread还是实现Runnable,都要有自 ...
- 线程池ThreadPoolExecutor实现原理
线程属于稀缺资源,对于线程的创建规则,引用<阿里巴巴 Java 手册>中的一条进行说明. 本篇从源码方面介绍ThreadPoolExecutor对象,并简要解析线程池工作原理. 首先Thr ...
- 线程池ThreadPoolExecutor使用原理
本文来源于翁舒航的博客,点击即可跳转原文观看!!!(被转载或者拷贝走的内容可能缺失图片.视频等原文的内容) 若网站将链接屏蔽,可直接拷贝原文链接到地址栏跳转观看,原文链接:https://www.cn ...
- 从源码角度来分析线程池-ThreadPoolExecutor实现原理
作为一名Java开发工程师,想必性能问题是不可避免的.通常,在遇到性能瓶颈时第一时间肯定会想到利用缓存来解决问题,然而缓存虽好用,但也并非万能,某些场景依然无法覆盖.比如:需要实时.多次调用第三方AP ...
- Java 线程池(ThreadPoolExecutor)原理分析与使用
在我们的开发中"池"的概念并不罕见,有数据库连接池.线程池.对象池.常量池等等.下面我们主要针对线程池来一步一步揭开线程池的面纱. 使用线程池的好处 1.降低资源消耗 可以重复利用 ...
随机推荐
- 我只会用threading,我菜
#服务端 import socket import threading # import multiprocessing server=socket.socket(socket.AF_INET,soc ...
- D3.js绘制 颜色:RGB、HSL和插值 (V3版本)
颜色和插值 计算机中的颜色,常用的标准有RGB和HSL. RGB:色彩模式是通过对红(Red).绿(Green).蓝(Blue)三个颜色通道相互叠加来得到额各式各样的颜色.三个通道的值得范围都 ...
- oh my zsh 如何启用插件
注 根据自己的需求启用插件.但是,插件具体实现什么功能就得自己看啦. 官网说明 实践 其实默认oh my zsh(以下简称zsh)已经在安装的时候就帮我们下载好了所有插件,只不过需要用户自己选择启用哪 ...
- Samza系统架构
- opencv2.4.9+vs2012安装配置
需要下载并安装vs2012 http://pan.baidu.com/s/1qXP76CO 第一次启动会提示要求输入激活序列号,请输入:YKCW6-BPFPF-BT8C9-7DCTH-QXG ...
- Bribing FIPA
Bribing FIPA 给出多棵有n个节点的有根树,第i个节点有一个权值\(a_i\),定义一个点能控制的点为其所有的子节点和它自己,询问选出若干个点的最少的权值之和,并且能够控制大于等于m个点,\ ...
- Linux CentOS-7.0上安装Tomcat7
Linux CentOS-7.0上安装Tomcat7 安装说明 安装环境:CentOS-7.0.1406安装方式:源码安装 软件:apache-tomcat-7.0.29.tar.gz 下载地址: ...
- Redis探索之路(七):Redis高级使用特性
一:安全性 设置客户端连接后进行任何其他指定前需要使用的密码. 因为Redis的速度非常之快,一台比较好的服务器下,一个外部的用户可以在1s内进行15万次的密码尝试连接,这就意味着你需要指定非常强大的 ...
- 【JZOJ3292】【BZOJ4415】【luoguP3988】发牌
description 在一些扑克游戏里,如德州扑克,发牌是有讲究的.一般称呼专业的发牌手为荷官.荷官在发牌前,先要销牌(burn card).所谓销牌,就是把当前在牌库顶的那一张牌移动到牌库底,它用 ...
- js面向对象的几种方式----工厂模式、构造函数模式、原型模式
对象的字面量 var obj={} 创建实例对象 var obj=new Object() 工厂模式 function cPerson(name,sex,age){ var o = new Objec ...