线程基础知识回顾
- 在 JVM 中用户线程结束了 ,JVM 就会关闭,并不会管守护线程
- 线程 setPriority() 的范围是 1-10 ,线程默认优先级是5
- 以前停止线程用的是 stop() ,现在不建议使用了。现在要停止线程,使用的是 interrupt() 和 isInterrupted()
线程的生命周期:新建、就绪、运行、阻塞、死亡
线程的几种状态(点开Thread类,有一个叫 State 的枚举,可以看见这几个状态):NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING
线程的生命周期和状态对应关系如下图:
线程池
什么是线程池?
Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中,合理地使用线程池能够带来3个好处:
① 降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
② 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
③ 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、 调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌。
线程池有什么作用?
线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。
如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜),况且我们还不能控制线程池中线程的开始、挂起、和中止
线程池的体系结构:
- java.util.concurrent.Executor 线程池的顶级接口定义了线程池的最基本方法
- java.util.concurrent.ExecutorService 定义常用方法
- java.util.concurrent.ThreadPoolExecutor 线程池的核心实现类
- java.util.concurrent.ScheduledThreadPoolExecutor 在核心实现类上扩展了定时和周期性的功能
- java.util.concurrent.ThreadPoolExecutor 线程池的核心实现类
- java.util.concurrent.ExecutorService 定义常用方法
- java.util.concurrent.Executors 线程池的工具类
他们的对应关系如下图:
现在我们大概就能知道这些类之间的关系了
从上可以看出,Executor 是线程池最基本的接口。他的实现如下,只定义了一个 execute() 方法,也就是说,我们线程池最核心的功能就是执行任务
public interface Executor {
void execute(Runnable command);
}
但是我们都不是用的 Executor 接口(他就像 Collection 一样,我们一般也用的 Collection 的子接口)
然后ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;
然后ThreadPoolExecutor继承了类AbstractExecutorService。
在ThreadPoolExecutor类中有几个非常重要的方法:
execute()
submit()
shutdown()
shutdownNow()
execute() 方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
submit() 方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。
shutdown() 和 shutdownNow() 都是用来关闭线程池的。区别就是:
executor.shutdown():等待任务队列所有的任务执行完毕后才关闭
executor.shutdownNow():立刻关闭线程池
还有很多其他的方法:
比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法,有兴趣的朋友可以自行查阅API。
以上就是对线程池的概述,现在我们进入线程池的重点内容—线程池的核心实现类ThreadPoolExecutor的讲解
线程池的 API 使用起来非常简单,三步就可以了,如下
有两种方法可以创建线程池,但是我们通常使用下面这种,即new ThreadPoolExecutor();
还有一种就是通过线程池的工具类的四种方法来创建线程池
//1. 创建一个线程池
ExecutorService es=new ThreadPoolExecutor();
//2. 执行我们想要被执行的任务
es.execute();
//3. 关闭线程池
es.shutdown();
那我们先来谈谈第一种创建线程池的方法:new ThreadPoolExecutor();
因为其实通过工具类来创建线程池,最后也是调用了这个方法
先来看下 ThreadPoolExecutor的七大参数
线程池的七大参数
面试的时候,我们着重了解一下线程池的原理 和 ThreadPoolExecutor()各个参数的含义
首先来说线程池的工作原理,ThreadPoolExecutor 中这7个参数是很重要的,面试常见
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
下面解释下一下构造器中各个参数的含义:
-
int corePoolSize
核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
-
int maximumPoolSize
线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程。只有在workQueue 也满了的时候,才会创建除了 corePoolSize 以外的线程,当大于 maximumPoolSize 的时候就要使用线程池的拒绝策略 RejectedExecutionHandler
-
BlockingQueue workQueue
用来暂时保存任务的工作队列 。当大于corePoolSize 的时候,任务就暂时放在这个队列里面,等 corePoolSize空闲的时候取出去执行,当这个队列满了的时候就会触发 maximumPoolSize。一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
- SynchronousQueue:
SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素。
使用SynchronousQueue阻塞队列一般要求maximumPoolSizes为无界,避免线程拒绝执行操作。 - LinkedBlockingQueue:
LinkedBlockingQueue是一个无界缓存等待队列。当前执行的线程数量达到corePoolSize的数量时,剩余的元素会在阻塞队列里等待。(所以在使用此阻塞队列时maximumPoolSizes就相当于无效了),每个线程完全独立于其他线程。生产者和消费者使用独立的锁来控制数据的同步,即在高并发的情况下可以并行操作队列中的数据。 - ArrayBlockingQueue:
ArrayBlockingQueue是一个有界缓存等待队列,可以指定缓存队列的大小,当正在执行的线程数等于corePoolSize时,多余的元素缓存在ArrayBlockingQueue队列中等待有空闲的线程时继续执行,当ArrayBlockingQueue已满时,加入ArrayBlockingQueue失败,会开启新的线程去执行,当线程数已经达到最大的maximumPoolSizes时,再有新的元素尝试加入ArrayBlockingQueue时会报错。
这三种队列的区别,主要就是SynchronousQueue只能有一个任务,ArrayBlockingQueue只能有固定数量个任务,LinkedBlockingQueue可以有无数个任务。ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
- SynchronousQueue:
-
RejectedExecutionHandler handler
当ThreadPoolExecutor已经关闭或ThreadPoolExecutor已经饱和时(达到了最大线程池的大小且工作队列已满),execute()方法将要调用的拒绝策略,有以下四种取值【四大拒绝策略】:
//1. 丢弃任务并抛出RejectedExecutionException异常 ThreadPoolExecutor.AbortPolicy //2. 也是丢弃任务,但是不抛出异常 ThreadPoolExecutor.DiscardPolicy //3. 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.DiscardOldestPolicy //4. 由调用线程处理该任务 ThreadPoolExecutor.CallerRunsPolicy
-
long keepAliveTime
表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
-
ThreadFactory threadFactory
线程工厂,主要用来创建线程,一般不管这个参数,使用默认值;
-
TimeUnit unit
参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天 TimeUnit.HOURS; //小时 TimeUnit.MINUTES; //分钟 TimeUnit.SECONDS; //秒 TimeUnit.MILLISECONDS; //毫秒 TimeUnit.MICROSECONDS; //微妙 TimeUnit.NANOSECONDS; //纳秒
Executor 相当于集合里面的 Collection
Executors 相当于集合里面的 Collections
类比集合,让我们看看线程池的这个工具类 — Executors,另一种创建线程池的方法
线程池的工具类
除了上面通过ThreadPoolExecutor 创建线程池,我们还可以通过线程池的工具类创建线程池,但是阿里不推荐这种创建线程池的方法,最好用上面的那张方法创建线程池,阿里规范这样讲:
【强制】线程池不允许使用
Executors
去创建,而是通过ThreadPoolExecutor
的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors
返回的线程池对象的弊端如下:
FixedThreadPool
和SingleThreadPool
: 允许的请求队列
长度为Integer.MAX_VALUE
,可能会堆积大量的请求,从而导致OOM
CachedThreadPool
和ScheduledThreadPool
: 允许的创建线程数量为Integer.MAX_VALUE
,可能会创建大量的线程,从而导致OOM
。
Executors是线程池的工具类,提供了四种快捷创建线程池的方法:
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程,核心线程数是0,最大线程数是Integer.MAX_VALUE,使用的SynchronousQueue队列。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
案例演示:
/**
* 创建一个定长线程池,支持延迟及周期性任务执行。延迟执行示例代码如下
*/
public void fun4(){
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
// 周期性执行任务(任务会执行多次)
// 参数1:任务 参数2:延迟时间 参数3:每隔长时间 参数4:时间单位
newScheduledThreadPool.scheduleAtFixedRate(()-> System.out.println("要执行的任务"), 3, 2, TimeUnit.SECONDS);
// 延迟执行任务(任务执行一次)
// 参数1:任务 参数2:延迟时间 参数3:时间单位
newScheduledThreadPool.schedule(()->System.out.println("要执行的任务"), 3,TimeUnit.SECONDS);
}
线程池的工作流程
-
流程1 判断核心线程数
-
流程2 判断任务能否加入到任务队列
-
流程3 判断最大线程数量
-
流程4 根据线程池的拒绝策略处理任务
从源码角度深入理解线程池原理
private final HashSet<Worker> workers = new HashSet<Worker>();
我们所说的线程池其实指的就是他,这个 workers。他就是存放工作线程的集合
Worker 代表一个工作线程,是ThreadPoolExecutor的一个内部类
// Worker工作者 代表一个工作线程
// Worker 是线程池的内部类, 它实现了Runnable接口
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
// Worker中的两个重要属性:
// 很重要的参数,说 Worker 是个工作线程,指的就是这个thread
final Thread thread; // 具体的工作线程
Runnable firstTask; //创建worker时,传入第一次要运行的任务【线程是可以复用的,这里指第一次使用】
volatile long completedTasks;
//Worker 的构造器:
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;// 创建worker时,传入第一次要运行的任务
// 通过线程池工厂创建了一个线程
/*
newThread(this) 这个 this 指的是Worker,说明只要Thread线程
一启动,就会调用Worker的 run 方法【Worker实现了Runnable接口】
,先看下下面的run方法
*/
this.thread = getThreadFactory().newThread(this);
}
public void run() {
// 看下面的 runWorker 方法
runWorker(this);
}
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
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或者
getTask()还能获得任务,就会继续执行循环
getTask() 是从哪里取得任务呢?看下面的getTask源码……
*/
while (task != null || (task = getTask()) != null) {
w.lock();
//这段if 是在判断线程池的状态的
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
//注意这个run方法
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);
}
}
getTask() 方法:
// Worker线程启动后,会不断的使用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 {
/* 看到这个workQueue 了吗? getTask()就是从workQueue
取出来的任务,就是我们前面讲过的任务队列
*/
// poll 和 take 是两种取任务的方法
Runnable r = timed ?
/*
poll 不是阻塞的方法,一般用于临时线程【>coreSize && <=MaxSize的那些线程】,
超过keepAliveTime的时间就停止获取任务了
*/
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
// 是阻塞的,用于核心线程获取任务
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
大概看完上面的Worker,了解到这个是线程池工作的一个核心的类,Worker里面有一个Thread线程,这个Thread运行的时候,会调用 Worker 的run() 方法,这个run方法里面的那个while 循环就是实现线程复用的关键,while (task != null || (task = getTask()) != null) ,getTask() 是从工作队列【workQueue】里面获取任务的一个方法,也就是说只要工作队列还有任务,while 循环就不会退出。
在getTask()方法里面,我们又看到了线程获取任务的两种方法,poll 和 take ,前者是非阻塞的,针对临时线程,后者是阻塞的,用于核心线程,因为临时线程超过keepAliveTime的时候,线程就会被销毁,而核心线程可以一直活着,一直等,等工作队列来任务
大概了解了Worker 之后,思考:Worker什么时候会被创建呢?创建完了之后,他里面的Thread 什么时候会被启动呢?
让我们分析一下 ThreadPoolExecutor 里面的execute方法,你可能就明白了
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// AtomicInteger ctl 是一个integer的原子类对象
// 主要作用: 1.记录线程池的状态信息 2.记录线程池工作线程的数量
int c = ctl.get();
// workerCountOf(c) : 工作线程的数量
// isRunning(c): 线程池是否运行状态
// 流程1: 工作线程的数量如果小于核心线程的数量,就添加一个worker。
//看完这个方法再去分析addWorker方法
/*
在addWorker方法里面其实就是创建了一个Worker,就是再多增加条线程吧
addWorker方法 参数1:要执行的任务 参数2: true代表添加核心线程
*/
if (workerCountOf(c) < corePoolSize) {
// 第二个参数为true,代表添加的是核心线程,否则添加的是临时线程
if (addWorker(command, true))
return;
c = ctl.get();
}
// 流程2: 尝试向任务队列workQueue中添加一个任务
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: 添加一个worker, 参数1: 要执行的任务 参数2:false代表添加临时线程
else if (!addWorker(command, false))
// 流程4: 线程池已饱和无法在创建新的worker,执行饱和策略
reject(command); // 调用具体的拒绝策略
}
总结一下这个execute() 方法就是:
① 判断这个任务是不是null,为null 就抛出异常
② 判断当前线程池中的线程数目是不是小于核心线程数,如果是,就再添加一个线程【或者说再创建一个Worker】
③ 在②不满足的时候,也就是说当超过核心线程数目的时候,再来的任务应该放在workQueue中
④ 当workQueue 也放不下的时候,就创建临时线程。else if (!addWorker(command, false)) ,就是说临时线程也不能创建成功的时候,就执行拒绝策略
再来看看上面多次执行的addWorker()方法是如何创建worker的:
// boolean 值为true,代表添加的是一个核心线程,否则,添加的是一个临时线程
private boolean addWorker(Runnable firstTask, boolean core) {
.....删掉了一部分代码
for (;;) {
//wc 取出当前工作线程的数量
int wc = workerCountOf(c);
// core=true 判断是否大于等于 corePoolSize
// core=false 判断是否大于等于 maximumPoolSize
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 从这里开始准备创建Worker
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 通过构造器得到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();
//把新的Worker添加到workers集合中【workers是个HashSet】
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
//添加成功的标志设置为true
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//如果新的Worker添加成功,启动该worker中的线程
/* 这个t 就是work 里面的线程,也就是说,在addWork()
方法里面,Worker被创建呢,Thread 会被启动
【回答了上面提出来的问题】
*/
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
记录一篇写的很好的文章:
https://www.cnblogs.com/dolphin0520/p/3932921.html