线程池
线程池状态
ThreadPoolExecutor使用int的高3位来表示线程池状态,低 29 位表示线程数量。
| 状态名 | 高 3位 | 接收新任务 | 处理阻塞队列任务 | 说明 |
|---|---|---|---|---|
| RUNNING | 111 | Y | Y | |
| SHUTDOWN | 000 | N | Y | 不会接收新任务,但会处理阻塞队列剩余任务 |
| STOP | 001 | N | N | 会中断正在执行的任务,并抛弃阻塞队列任务 |
| TIDYING | 010 | - | - | 任务全执行完毕,活动线程为 0 即将进入终结 |
| TERMINATED | 011 | - | - | 终结状态 |
从数字上比较,TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING
这些信息存储在一个原子变量 ctl 中,目的是将线程池状态与线程个数合二为一,这样就可以用一次 cas 原子操作进行赋值。
// 解压runState 线程池状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 解压workerCount 线程池数量
private static int workerCountOf(int c) { return c & CAPACITY; }
// 打包ctl rs:runState wc:workThreadCount
// rs 为高 3 位代表线程池状态, wc 为低 29 位代表线程个数
// ctlOf方法 是合并它们
private static int ctlOf(int rs, int wc) { return rs | wc; }
// CAS更新线程池状态和线程数量 c 为旧值, ctlOf 返回结果为新值
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))));
线程池流程图
线程池工作方式
1.线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。
2.当线程数达到 corePoolSize=2时,此时并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排队,直到有空闲的线程。
3.如果队列选择了有界队列,那么任务超过了队列大小时,会创建 (maximumPoolSize - corePoolSize)=1 数目的线程来救急,即救急线程。
4.如果线程到达maximumPoolSize,仍然有新任务这时会执行拒绝策略,拒绝策略jdk提供了4种实现.
线程池构造方法
主要是ThreadPoolExecutor。
/***
corePoolSize 核心线程数目 (最多保留的线程数)
maximumPoolSize 最大线程数目
keepAliveTime 生存时间 - 针对救急线程
unit 生存时间单位 - 针对救急线程
workQueue 阻塞队列(有界队列||无界队列)
threadFactory 线程工厂 - 可以为线程创建时起个好名字
handler 拒绝策略
***/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
//自定义拒绝策略
ThreadPoolExecutor.AbortPolicy abortPolicy = new
ThreadPoolExecutor.AbortPolicy();
//创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
1,
2,
60 * 1000,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(10),
abortPolicy);
Executors 创建线程
- corePool :Size核心线程数目(最多保留的线程数)
- maximunPoolSize :最大线程数目
- keepAliveTIme :生存时间- 针对救急线程
- unit :时间单位 - 针对救急线程
- workQueue :阻塞队列
- threadFactory :线程工厂 - 可以为线程创建时起个好名字
- handler :拒绝策略
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
根据这个构造方法,JDK Executors 类中提供了众多工厂方法来创建各种用途的线程池。
newFixedThreadPool:没有救急线程&无界
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads,
nThreads,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
它是一种固定大小的线程池。
核心线程数 == 最大线程数==nThreads;即核心线程数量将永远维持在nThreads。
keepAliveTime=0,意味着一旦有多余的空闲线程,就会被立即停止掉。
核心线程数 == 最大线程数(即最大线程数-核心线程数=0 即不会有救急线程被创建),因此也无需超时时间。
阻塞队列采用了LinkedBlockingQueue,它是一个无界队列,可以放任意数量的任务。
由于阻塞队列是一个无界队列,因此永远不可能拒绝任务。
场景:适用于任务量已知,相对耗时的任务。
newCachedThreadPool:全是救急线程,有界
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());//有界队列
}
核心线程数是0,最大线程数是 Integer.MAX_VALUE,救急线程的空闲生存时间是 60s,意味着全部都是救急线程(60s 后可以回收),救急线程可以无限创建。
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
队列采用了 SynchronousQueue 实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手交货)。
这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程。
package 线程池;
import java.util.concurrent.SynchronousQueue;
import static java.lang.Thread.sleep;
public class TestSynchronousQueue {
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<Integer> integers = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println("我要放入1个" + 1);
integers.put(1);
System.out.println("放入1个" + 1 + "成功");
System.out.println();
System.out.println("我要放入1个" + 2);
integers.put(2);
System.out.println("放入1个" + 2 + "成功");
System.out.println();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() -> {
try {
Thread.sleep(3000);
System.out.println("我睡了3秒再来取");
System.out.println("taking {}" + 1);
integers.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2").start();
new Thread(() -> {
try {
Thread.sleep(4000);
System.out.println("我睡了4秒再来取");
System.out.println("taking {}" + 2);
integers.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t3").start();
}
}
我要放入1个1
//执行 integers.put(1);
我睡了3秒再来取
taking {}1
放入1个1成功
我要放入1个2
//执行 integers.put(2);
我睡了4秒再来取
taking {}2
放入1个2成功
可以发现SynchronousQueue队列在放入的时候,会被阻塞,直到有线程来取的时候才能放进去
任务到来,有空闲线程则使用空闲线程,无空闲线程则创建。所以称之为缓存线程
任务处理速度 > 任务提交速度 ,这样才能保证不会不断创建新的进程,避免内存被占满。
整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线程。
适合任务数比较多,但是单个任务耗时较短的场景。
newSingleThreadExecutor:核线1,无救急&无界
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。
创建线程池的正确姿势
1.ThreadPoolExecutor
public class ExecutorsDemo {
//ThreadFactoryBuilder用于给线程起名字。
private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024),
namedThreadFactory,
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
pool.execute(new SubThread());
}
}
}
通过上述方式创建线程时,不仅可以避免OOM的问题,还可以自定义线程名称,更加方便的出错的时候溯源。
2.Hutool:ThreadFactoryBuilder
火遍全网的Hutool,如何使用Builder模式创建线程池 |牛气冲天新年征文
我们依照线程池来举例,默认创建的线程池,构造方法最多有七个参数,核心线程数、最大线程数、阻塞队列、线程存活时间...
日常使用创建线程池时,大家想一下为什么要这么设计?一起来看下源码注释中如何解释此行为
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
线程池之所以设置如此之多的构造参数,是因为对这些参数会有一定规则的校验,如果不满足线程池的规则,将不允许创建线程池,通过抛异常的方式终止程序
终止规则大概有七点,这里列举一下:
- 核心线程数不可以小于 0
- 线程存活时间不可以小于 0
- 最大线程数不可以小于等于 0,同时也不可以小于核心线程数
- 阻塞队列、线程工厂、拒绝策略参数均不可为空
上述七点有两个作用:其一是为了让核心参数满足线程池运行流程,其二是为了保障运行时的稳定性
小伙伴想一哈线程池创建是不是灰常灰常适合 Builder 模式。
1.构造器函数过多
2.属性之间存在依赖关系和约束条件。
Hutool包下的ThreadFactoryBuilder使用了建造者模式,回顾一下建造者模式的场景和用法。
建造者模式
Builder 模式场景:如果类的属性之间有一定的依赖关系或者约束条件(源自设计模式之美) ,那么就可以考虑使用 Builer 设计模式
1.当被创建的对象内部成员之间相互依赖时,不能单纯的直接new出来。
2.当初始化参数比较多时,直接new出来,构造方法内的参数过多,代码不够简洁,容易出错。
3.直接new出来对象,再采用set方法为成员赋值时,可能会产生在所有需要set的属性被设置前,该对象处于无效状态,但我们需要是对象创建出来即生效;
4.如果需要一次性创建相互有依赖关系的多个对象时,就应该使用建造者模式。
5.Builder 模式,强调的是管理依赖关系或者约束条件
看一下ThreadFactoryBuilder的组成,可以清楚看见这是一个建造者模式的完整实现。我们可以使用builder来快速的构建一个线程对象。
建造者模式和工厂模式的区别
通过前面的学习,我们已经了解了建造者模式,那么它和工厂模式有什么区别呢?
- 建造者模式更加注重方法的调用顺序,工厂模式注重创建对象。
- 创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的对象都一样
- 关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,还要知道对象由哪些部件组成。
- 建造者模式根据建造过程中的顺序不一样,最终对象部件组成也不一样。
为什么禁止使用Executors
不建议使用Executors创建线程池,我提到的是『不建议』。
但是在阿里巴巴Java开发手册中也明确指出,而且用的词是『不允许』使用Executors创建线程池。
/***
线程池不允许使用Executors去创建,而应该通过ThreadPoolExecutor方式,这样处理方式更加明确线程池运行规则,规避资源耗尽风险。
FixedThreadPool SingleThreadPool
允许请求队列长度为Integer.MAX_VALUE可能会堆积大量请求从而导致OOM
CachedThreadPool ScheduledThreadPool
允许创建线程数量为Integer.MAX_VALUE可能会创建大量线程从而导致OOM
***/
在阿里巴巴Java开发手册中提到,使用Executors创建线程池可能会导致OOM(OutOfMemory ,内存溢出)。
但是并没有说明为什么,那么接下来我们就来看一下到底为什么不允许使用Executors?
无界队列导致OOM
public class ExecutorsDemo {
private static ExecutorService executor = Executors.newFixedThreadPool(15);
public static void main(String[] args) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executor.execute(new SubThread());
}
}
}
class SubThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//do nothing
}
}
}
//通过指定JVM参数:-Xmx8m -Xms8m 运行以上代码,会抛出OOM:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)
通过上面的例子,我们知道了Executors。创建的线程池存在OOM的风险,那么到底是什么原因导致的呢?我们需要深入Executors的源码来分析一下。
其实,在上面的报错信息中,我们是可以看出蛛丝马迹的,在以上的代码中其实已经说了,真正的导致OOM的其实是LinkedBlockingQueue.offer。
如果读者翻看代码的话,也可以发现,其实底层确实是通过LinkedBlockingQueue.offer。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
Java中的BlockingQueue主要有两种实现,分别是ArrayBlockingQueue和LinkedBlockingQueue
ArrayBlockingQueue是一个用数组实现的有界阻塞队列,必须设置容量。
LinkedBlockingQueue是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列。
这里的问题就出在:不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。也就是说,如果我们不设置LinkedBlockingQueue的容量的话,其默认容量将会是Integer.MAX_VALUE。 而newFixedThreadPool中创建LinkedBlockingQueue时,并未指定容量。此时,LinkedBlockingQueue就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出问题。
上面提到的问题主要体现在newFixedThreadPool和newSingleThreadExecutor两个工厂方法上。
最大线程数导致OOM
但并不是说newCachedThreadPool和newScheduledThreadPool这两个方法就安全了。这两种方式创建的最大线程数可能是Integer.MAX_VALUE,而创建这么多线程,必然就有可能导致OOM。避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池。在创建的同时,给BlockQueue指定容量就可以了。
//注意这里使用的是数组实现的有界阻塞队列ArrayBlockingQueue
private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue(10));
这种情况下,一旦提交的线程数超过当前可用线程数时,默认的拒绝策略AbortPolicy就会抛出java.util.concurrent.RejectedExecutionException。
这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。
同时我们可以自定义自己的拒绝策略替换默认的AbortPolicy拒绝策略。
BlockingQueue
总是记不住put和take以及offer和poll,再贴一遍!
BlockingQueue接口简介
java.util.concurrent 包里的 BlockingQueue是一个接口, 继承Queue接口,Queue接口继承 Collection。
BlockingQueue 具有 4 组不同的方法用于插入、移除以及对队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下:
| 抛出异常 | 特殊值 | 阻塞 | 超时 | |
|---|---|---|---|---|
| 插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
| 移除 | remove() | poll() | take() | poll(time, unit) |
| 检查 | element() | peek() | 不可用 | 不可用 |
- 插入操作 add(e) :添加元素到队列中,如果队列满了,继续插入元素会报错,IllegalStateException。 offer(e) : 添加元素到队列,同时会返回元素是否插入成功的状态,如果成功则返回 true put(e) :当阻塞队列满了以后,生产者继续通过 put添加元素,队列会一直阻塞生产者线程,知道队列可用 offer(e,time,unit) :当阻塞队列满了以后继续添加元素,生产者线程会被阻塞指定时间,如果超时,则线程直接退出
- 移除操作 remove():当队列为空时,调用 remove 会返回 false,如果元素移除成功,则返回 true poll(): 当队列中存在元素,则从队列中取出一个元素,如果队列为空,则直接返回 null take():基于阻塞的方式获取队列中的元素,如果队列为空,则 take 方法会一直阻塞,直到队列中有新的数据可以消费 poll(time,unit):带超时机制的获取数据,如果队列为空,则会等待指定的时间再去获取元素返回
ArrayBlockingQueue和LinkedBlockingQueue
- 队列中锁的实现不同
- ArrayBlockingQueue实现的队列中的锁是没有分离的,即生产和消费是同一把锁;
- LinkedBlockingQueue实现的队列中的锁是分离的,即生产用的putLock,消费用的takeLock
- 在生产或消费时操作不同
- ArrayBlockingQueue实现的队列在生产和消费时,是直接将数据对象插入或者移除数组;
- LinkedBlockingQueue实现的队列在生产和消费时,需要把数据对象转换为Node进行插入或移除
- 队列大小初始化方式不同
- ArrayBlockingQueue实现的队列必须指定大小;
- LinkedBlockingQueue实现的队列可以不指定队列的大小,但是默认是Integer.MAX_VALUE
具体参考:
拒绝策略
默认的拒绝策略是
private static final RejectedExecutionHandler defaultHandler =new AbortPolicy();
AbortPolicy:丢弃任务并抛出异常。
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
你就直接抛出1个异常啊???!
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException
("Task " + r.toString() + " rejected from " + e.toString());
}
}
DiscardPolicy:丢弃任务不抛出异常。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
妈的是1个空方法!
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
DiscardOldestPolicy:丢弃队列头任务,重新提交被拒绝的任务
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
CallerRunsPolicy:由提交任务的线程处理该任务
ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。
直接调用run方法。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
一般开发中这几种策略都不会用。通常我们会自定义自己的拒绝策略,把被拒绝的任务的相关信息放到数据库或者消息中间件。
然后通过跑批或者消费消息的方式进行重试。
自定义拒绝策略
实现RejectedExecutionHandler
package com.bjsxt.height.concurrent018;
import java.net.HttpURLConnection;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
public class MyRejected implements RejectedExecutionHandler{
public MyRejected(){
}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
//使用数据库持久化 或者 发送到消息中间件
log.info("当前被拒绝任务为:" + r.toString());
}
}
MyRejected policy = new MyRejected();
//使用自定义拒绝策略创建线程池
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(1, 2, 60 * 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10), policy);
线程工厂:ThreadFactory
在线程池中创建线程使用的是线程工厂。
public interface ThreadFactory {
// 构造一个新的Thread 实现时可以初始化优先级、名称、守护进程状态、 ThreadGroup等
Thread newThread(Runnable r);
}
在线程池中使用给定的第一个Runnable使用线程工程创建线程
/**
* 使用给定的第一个Runnable使用线程工程创建线程
*/
Worker(Runnable firstTask) {
// inhibit interrupts until runWorker
setState(-1);
this.firstTask = firstTask;
//获取线程工厂并创建线程
this.thread = getThreadFactory().newThread(this);
}
DefaultThreadFactory(默认)
namePrefix = "pool-" +poolNumber.getAndIncrement() + "-thread-";
DefaultThreadFactory位于java.util.concurrent.Executors.DefaultThreadFactory,是1个静态内部类。
由于默认的线程工厂无法区分不同的线程池中的线程,因此不推荐使用。
// 默认线程工厂
static class DefaultThreadFactory implements ThreadFactory {
// 线程池号
private static final AtomicInteger poolNumber = new AtomicInteger(1);
// 线程组
private final ThreadGroup group;
// 线程号
private final AtomicInteger threadNumber = new AtomicInteger(1);
// 线程名称前缀
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
// 获取线程组
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
// 线程名称前缀 = pool + 线程池号 + thread
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
// 创建新的线程
public Thread newThread(Runnable r) {
// 创建线程(线程组,任务,线程名称)
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
// 判断当前查询是否是守护线程
// 如果是守护线程则设置为非守护线程
if (t.isDaemon())
t.setDaemon(false);
// 判断线程的优先级是否为默认优先级
if (t.getPriority() != Thread.NORM_PRIORITY)
// 将线程优先级设置为默认优先级
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
CustomizableThreadFactory(推荐)
//指定前缀
ThreadFactory threadFactory = new CustomizableThreadFactory("data-sync-pool-");
CustomizableThreadFactory源码
public class CustomizableThreadFactory extends CustomizableThreadCreator implements ThreadFactory {
public CustomizableThreadFactory() {
super();
}
//设置线程前缀
public CustomizableThreadFactory(String threadNamePrefix) {
super(threadNamePrefix);
}
//重写ThreadFactory#newThread
@Override
public Thread newThread(Runnable runnable) {
return createThread(runnable);
}
}
public class CustomizableThreadCreator implements Serializable {
private String threadNamePrefix;
private int threadPriority = 5;
private boolean daemon = false;
@Nullable
private ThreadGroup threadGroup;
private final AtomicInteger threadCount = new AtomicInteger(0);
public CustomizableThreadCreator() {
this.threadNamePrefix = this.getDefaultThreadNamePrefix();
}
//主要看这个构造方法 如果设置了线程前缀就使用设置好的线程前缀 否则使用默认的线程前缀
//默认的线程前缀是:ClassUtils.getShortName(this.getClass()) + "-"
public CustomizableThreadCreator(@Nullable String threadNamePrefix) {
this.threadNamePrefix = threadNamePrefix != null ? threadNamePrefix : this.getDefaultThreadNamePrefix();
}
public void setThreadNamePrefix(@Nullable String threadNamePrefix) {
this.threadNamePrefix = threadNamePrefix != null ? threadNamePrefix : this.getDefaultThreadNamePrefix();
}
public String getThreadNamePrefix() {
return this.threadNamePrefix;
}
public void setThreadPriority(int threadPriority) {
this.threadPriority = threadPriority;
}
public int getThreadPriority() {
return this.threadPriority;
}
//在设置守护线程方面 提供了批量设置的方法 但是感觉不够灵活
public void setDaemon(boolean daemon) {
this.daemon = daemon;
}
public boolean isDaemon() {
return this.daemon;
}
public void setThreadGroupName(String name) {
this.threadGroup = new ThreadGroup(name);
}
public void setThreadGroup(@Nullable ThreadGroup threadGroup) {
this.threadGroup = threadGroup;
}
@Nullable
public ThreadGroup getThreadGroup() {
return this.threadGroup;
}
public Thread createThread(Runnable runnable) {
Thread thread = new Thread(this.getThreadGroup(), runnable, this.nextThreadName());
thread.setPriority(this.getThreadPriority());
thread.setDaemon(this.isDaemon());
return thread;
}
protected String nextThreadName() {
return this.getThreadNamePrefix() + this.threadCount.incrementAndGet();
}
protected String getDefaultThreadNamePrefix() {
return ClassUtils.getShortName(this.getClass()) + "-";
}
}
线程池执行任务API
// 执行任务
void execute(Runnable command);
// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);
// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
线程池执行任务示例
package 线程池;
import java.util.concurrent.*;
public class Futuretask2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//ThreadPoolExecutor extends AbstractExecutorService
//abstract class AbstractExecutorService implements ExecutorService
//此处 Executors.newFixedThreadPool返回的是ThreadPoolExecutor
//使用ExecutorService没毛病
//并且AbstractExecutorService提供了1个execute方法 该方法来自接口ExecutorService
//AbstractExecutorService并没有实现execute方法 而是交由具体的子类实现
//典型的模板模式
ExecutorService executorService = Executors.newFixedThreadPool(1);
Integer result = 10;
Runnable runnable = new Runnable() {
@Override
public void run() {
}
};
//第一种方式 直接执行runnable 其实调用的子类的execute方法
executorService.execute(runnable);
System.out.println(result);
//第二种方式 执行的是futureTask
//futureTask重写了run方法 在run方法执行的是callable的call方法
//在构造futureTask的时候使用了适配器模式 将runnable适配为RunnableAdapter
FutureTask<Integer> futureTask = new FutureTask<Integer>(runnable, result);
// public class FutureTask<V> implements RunnableFuture<V>
// public interface RunnableFuture<V> extends Runnable, Future<V>
executorService.execute(futureTask);
//如果使用的是runnable,返回的结果是我们预先设置好的结果
System.out.println(futureTask.get());
//第三种方式执行FutureTask
//futureTask重写了run方法 在run方法执行的是callable的call方法
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() {
return 100;
}
};
FutureTask<Integer> futureTask2 = new FutureTask<Integer>(callable);
executorService.execute(futureTask2);
System.out.println(futureTask2.get());
}
}
execute和submit区别
1.execute只能提交Runnable类型的任务,没有返回值,而submit既能提交Runnable类型任务也能提交Callable类型任务,返回Future类型。
2.execute方法提交的任务异常是直接抛出的,而submit方法是是捕获了异常的,当调用FutureTask的get方法时,才会抛出异常。
3.submit方法调用了execute方法
推荐使用submit
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
//适配器模式
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
//适配器模式
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
//适配器模式
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
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();
}
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);
}
execute
public void execute(Runnable command) {
//(1) 如果任务为null,则抛出NPE异常
if (command == null)
throw new NullPointerException();
//(2)获取当前线程池的状态+线程个数变量的组合值
int c = ctl.get();
//(3)当前线程池线程个数是否小于corePoolSize,小于则新建核心线程
if (workerCountOf(c) < corePoolSize) {
//addWorker返回false则不会创建核心线程
//1.线程状态为 STOP,TIDYING,TERMINATED SHUTDOWN
//2.当前线程数大于等于核心线程数
//返回true代表成功创建了核心线程且核心线程已经在执行任务
//返回的的参数true代表创建的是核心线程 false代表的是创建的是救急线程
//<<<<<<<<重点方法addWorker>>>>>>>>>>
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果返回的是false 说明核心线程大于等于最大
//(4)如果线程池处于RUNNING状态,则添加任务到阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
//(4.1)二次检查
int recheck = ctl.get();
//(4.2)如果当前线程池状态不是RUNNING则从队列删除任务,并执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
//(4.3)否则如果当前线程池线程空,则添加一个线程
//因为核心线程数可能是0
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//(5)如果队列满了,则新增救急线程,新增救急线程失败则执行拒绝策略
//如果核心线程数是0 那么每次都会创建一个救急线程
else if (!addWorker(command, false)){
//创建救急线程失败拒绝任务
reject(command);
}
}
submit
可以看到submit只是将Callable做了一层封装,封装成了 RunnableFuture,然后调用的还是excute方法。
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
原理:FutureTask的底层实现
代码案例
public class Futuretask2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() {
return 100;
}
};
FutureTask<Integer> futureTask = new FutureTask<Integer>(callable) {
@Override
protected void done() {
System.out.println("done");
}
};
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
}
//done
//100
FutureTask#run
先概述一下原理。
FutureTask实现了Runnable接口,在FutureTask的run方法中会调用callable的call方法。
在callable的call方法执行完毕立刻设置标志位。
然后会根据执行完毕标志位,判断是否需要调用set方法会设置响应结果并修改状态。
然后会遍历所有在队列等待结果的线程,unpark唤醒这些阻塞在get方法中的线程,
那我们重点看下它的run方法。
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
//1.获取callable 这里的callable可能是1个RunnableAdapter
Callable<V> c = callable;
//2.判断状态是否是新建
if (c != null && state == NEW) {
V result;
boolean ran;
try {
//3.调用run方法
result = c.call();
//修改是否执行完的标志位
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
//4.判断是否执行完且没有异常
if (ran)
//5.重点:设置结果
set(result);
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
FutureTask#set
protected void set(V v) {
//cas 设置状态为COMPLETING
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
//设置结果
outcome = v;
//设置最终状态为NORMAL 即正常
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
//调用finishCompletion方法
finishCompletion();
}
}
FutureTask#finishCompletion
private void finishCompletion() {
// assert state > COMPLETING;
//遍历等待获取结果的线程并依次通知 从队列中依次取出所有等待的线程
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
//取出等待的线程
Thread t = q.thread;
if (t != null) {
q.thread = null;
//在这里将之前阻塞的线程unpark。
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
//空实现 扩展方法 模板模式
done();
callable = null; // to reduce footprint
}
FutureTask#get
public V get() throws InterruptedException, ExecutionException {
int s = state;
//如果状态小于等于2 即未完成 调用awaitDone 陷入阻塞
if (s <= COMPLETING){
s = awaitDone(false, 0L);
}
//如果状态大于2即NORMAL会调用report
return report(s);
}
为每个FutureTask设置了1个状态。枚举如下:
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
FutureTask#awaitDone
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
//死循环遍历
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
//如果已经执行完成直接返回状态s
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
//执行中就礼让
}else if (s == COMPLETING) // cannot time out yet
Thread.yield();
//如果q为空说明还未初始化 需要新创健1个WaitNode
else if (q == null)
q = new WaitNode();
//等待线程入队
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
//带超时时间
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}else
//park等待
LockSupport.park(this);
}
}
这里是一个死循环,首先判断了一个状态,然后入队,调用了LockSupport.park方法让这个线程阻塞,就是说你get的时候,如果我没执行完,那你就在这等着我执行完。
其他的线程调用本线程的LockSupport.unpark也不会让本线程退出等待,而是会在下一轮循环继续等待。一直等到状态变为大于COMPLETING,然后调用report(s)。
FutureTask#report
直接返回结果给调用get方法的线程
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
关闭线程池
shutdown
它可以安全地关闭一个线程池,调用 shutdown() 方法之后线程池并不是立刻就被关闭,因为这时线程池中可能还有很多任务正在被执行,或是任务队列中有大量正在等待被执行的任务,调用 shutdown() 方法后线程池会在执行完正在执行的任务和队列中等待的任务后才彻底关闭。
调用 shutdown() 方法后如果还有新的任务被提交,线程池则会根据拒绝策略直接拒绝后续新提交的任务。
/*
-线程池状态变为 SHUTDOWN
- 不会接收新任务
- 空闲的线程可以立刻终结
- 但已提交任务会执行完,即会处理阻塞队列剩余任务
- 此方法不会阻塞调用线程的执行
*/
void shutdown();
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 修改线程池状态
advanceRunState(SHUTDOWN);
// 仅会打断空闲线程 idle:懈怠的; 懒惰的; 闲置的; 没有工作的; 闲散的;
interruptIdleWorkers();
onShutdown(); // 扩展点 ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 尝试终结(没有运行的线程可以立刻终结,如果还有运行的线程也不会等任务结束在返回)
tryTerminate();
//调用 shutdown 后,由于调用线程并不会等待所有任务运行结束,因此如果它想在线程池完全TERMINATED 后做些事情,可以利用此方法等待
//boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
}
shutdownNow
它和 shutdown() 的区别就是多了一个 Now,表示立刻关闭的意思,不推荐使用这一种方式关闭线程池。
在执行 shutdownNow 方法之后,首先会给所有线程池中的线程发送 interrupt 中断信号,尝试中断这些任务的执行,然后会将任务队列中正在等待的所有任务转移到一个 List 中并返回,我们可以根据返回的任务 List 来进行一些补救的操作,例如记录在案并在后期重试。
/*
线程池状态变为 STOP
- 不会接收新任务
- 会中断正在执行的任务,并用 interrupt 的方式中断正在执行的任务
- 会将队列中的任务返回
*/
List<Runnable> shutdownNow();
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 修改线程池状态
advanceRunState(STOP);
// 打断所有线程
interruptWorkers();
// 获取队列中剩余任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
// 尝试终结
tryTerminate();
return tasks;
}
shutdown和shutdownNow区别
shutdown 会等待线程池中的任务执行完成之后关闭线程池。shutdownNow 会给所有线程发送中断信号,中断任务执行,然后关闭线程池。
shutdown 没有返回值,而 shutdownNow 会返回关闭前任务队列中未执行的任务集合(List)
使用守护线程关闭线程池[没试过]
shutdownNow方式只能强制关闭能被interrupt的线程,对于某些不会发生InterruptedException的线程来说,是无法关闭线程池的。所以此时,我们可以在创建线程池时设置线程工厂中创建的线程是后台线程(默认的线程工厂创建的线程默认不是后台线程),这样主线程执行结束,那么线程池也就结束了。
使用标志位关闭线程池
创建线程池
public static ThreadPoolExecutor dataSyncAllExecutor = null;
ThreadFactory threadFactory = new CustomizableThreadFactory(DataSyncConstant.threadPoolPrefix);
dataSyncAllExecutor = new ThreadPoolExecutor(DataSyncConstant.threadPoolSize,
DataSyncConstant.threadPoolSize,
DataSyncConstant.keepAlive,
TimeUnit.MINUTES,
new ArrayBlockingQueue(DataSyncConstant.queueSize),
threadFactory);
创建任务
@Component
public class DataSyncRunnableFactoryBean implements FactoryBean {
@Value("${data.sync.tables}")
private List<String> tables;
@Autowired
DataSyncService dataSyncService;
@Override
public Object getObject() throws Exception {
return new DataSyncRunnable(dataSyncService, tables);
}
@Override
public Class<?> getObjectType() {
return DataSyncRunnable.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
@Data
@Accessors
@NoArgsConstructor
@AllArgsConstructor
@Slf4j
@SuppressWarnings("SpringJavaConstructorAutowiringInspection")
public class DataSyncRunnable implements Runnable {
private DataSyncService dataSyncService;
private String dataSourceKey;
private List<String> configTables;
public DataSyncRunnable(DataSyncService dataSyncService, List<String> configTables) {
this.dataSyncService = dataSyncService;
this.configTables = configTables;
}
@Override
public void run() {
//省略前后逻辑
dataSyncService.sync(dataSourceKey, table);
//省略前后逻辑
}
}
@Override
public void sync(String dataSourceKey, String table) {
//省略前后逻辑
if (DataSyncConstant.dataSyncAllIsStop) {
throw new RuntimeException("手动停止全量同步!");
}
//省略前后逻辑
}
提交任务到线程池
for (String dataSourceKey : dataBaseKey) {
key = dataSourceKey;
DataSyncRunnable runnable = (DataSyncRunnable) syncRunnableFactoryBean.getObject();
runnable.setDataSourceKey(dataSourceKey);
dataSyncAllExecutor.submit(runnable);
}
结束线程池
@Override
public List<Runnable> syncAllStop() {
new Thread(new Runnable() {
@Override
public void run() {
DataSyncConstant.dataSyncAllIsStop = true;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
//重置线程池状态
if (EmptyUtil.isNotEmpty(dataSyncAllExecutor)) {
List<Runnable> tasks = dataSyncAllExecutor.shutdownNow();
dataSyncAllExecutor = null;
DataSyncConstant.dataSyncAllIsStop = false;
}
DataSyncConstant.dataSyncAllIsStop = false;
}
}).start();
return null;
}
线程池的其它方法
// 不在RUNNING状态的线程池,此方法就返回 true
boolean isShutdown();
// 线程池状态是否是 TERMINATED
boolean isTerminated();
// 调用 shutdown 后,由于调用线程并不会等待所有任务运行结束
// 因此如果它想在线程池 TERMINATED 后做些事情,可以利用此方法等待
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
线程池源码
ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量。
| 状态名 | 高 3位 | 接收新任务 | 处理阻塞队列任务 | 说明 |
|---|---|---|---|---|
| RUNNING | 111 | Y | Y | |
| SHUTDOWN | 000 | N | Y | 不会接收新任务,但会处理阻塞队列剩余任务 |
| STOP | 001 | N | N | 会中断正在执行的任务,并抛弃阻塞队列任务 |
| TIDYING | 010 | - | - | 任务全执行完毕,活动线程为 0 即将进入终结 |
| TERMINATED | 011 | - | - | 终结状态 |
从数字上比较,TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING
// 解压runState 即 线程池状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 解压workerCount 即工作线程个数
private static int workerCountOf(int c) { return c & CAPACITY; }
// 打包线程池状态 和 工作线程个数
// rs 为高 3 位代表线程池状态, wc 为低 29 位代表线程个数,ctl 是合并它们
private static int ctlOf(int rs, int wc) { return rs | wc; }
// c 为旧值, ctlOf 返回结果为新值
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))));
线程池构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//最大线程数不能小于核心线程数
//原因:最大线程数 - 核心线程数 = 救急线程数
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
Execute
Worker
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
Worker既是线程也是锁,牛!
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
private static final long serialVersionUID = 6138294804551838833L;
/*当前worker所关联的线程对象,需要的时候也是直接获取即可*/
final Thread thread;
/*线程创建时的第一个任务*/
Runnable firstTask;
/*线程完成的任务数量,注意这边是volatile类型的*/
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
//注意这里 这里传入的runnable是woker自己
//也就意味着执行的是Woker的run方法的逻辑
this.thread = getThreadFactory().newThread(this);
}
/***
Worker的run方法 进入runWorker
***/
public void run() {
//<<<<<<<<<<<重点方法>>>>>>>>>>>
runWorker(this);
}
//<<<<<<<<<<<重点方法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 ((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 {
//处理worker退出
processWorkerExit(w, completedAbruptly);
}
}
必须在运行状态才能添加worker,addworker是在exute方法中执行的
public void execute(Runnable command) {
//(1) 如果任务为null,则抛出NPE异常
if (command == null)
throw new NullPointerException();
//(2)获取当前线程池的状态+线程个数变量的组合值
int c = ctl.get();
//(3)当前线程池线程个数是否小于corePoolSize,小于则新建核心线程
if (workerCountOf(c) < corePoolSize) {
//addWorker返回false则不会创建核心线程
//1.线程状态为 STOP,TIDYING,TERMINATED SHUTDOWN
//2.当前线程数大于等于核心线程数
//返回true代表成功创建了核心线程且核心线程已经在执行任务
//返回的的参数true代表创建的是核心线程 false代表的是创建的是救急线程
//<<<<<<<<重点方法addWorker>>>>>>>>>>
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果返回的是false 代码才能走到这里 说明核心线程已经大于等于最大
//(4)如果线程池处于RUNNING状态,则添加任务到阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
//(4.1)二次检查
int recheck = ctl.get();
//(4.2)如果当前线程池状态不是RUNNING则从队列删除任务,并执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
//(4.3)否则如果当前线程池线程空,则添加一个线程
//因为核心线程数可能是0
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//(5)如果队列满了,则新增救急线程,新增救急线程失败则执行拒绝策略
//如果核心线程数是0 那么每次都会创建一个救急线程
else if (!addWorker(command, false)){
//创建救急线程失败拒绝任务
reject(command);
}
}
addWorker
//core代表是否是核心线程
private boolean addWorker(Runnable firstTask, boolean core) {
//外层循环
//外层循环
//外层循环
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//(6) 检查队列是否只在必要时为空
//TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING
//返回false的3种情况
//1.当前线程池状态为 STOP,TIDYING,TERMINATED
//2.当前线程池状态为 SHUTDOWN 并且已经有了第一个任务即firtTask不为空;
//3.当前线程池状态为 SHUTDOWN 并且任务队列为空。
//返回false代表创建线程失败
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//7.循环cas增加线程个数
for (;;) {
//获取当前工作线程数量
int wc = workerCountOf(c);
//CAPACITY = 536870911
//core代表的是是否是核心线程
//如果是核心线程就用corePoolSize
//否则就是救急线程maximumPoolSize
//7.1如果线程个数大于536870911或者线程个数大于指定线程数则返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//7.2上面的判断没有进去 说明可以增加线程 cas增加线程个数,同时只有一个线程成功
//如果cas成功即 将c改为c+1 则 跳出外层循环
if (compareAndIncrementWorkerCount(c))
//跳出外层循环
//跳出外层循环
//跳出外层循环
break retry;
//7.3走到这里说明 cas失败了,则看线程池状态是否变化了
//变化则跳到外层循环重试重新获取线程池状态,否则内层循环重新cas。
c = ctl.get();
if (runStateOf(c) != rs)
//继续外层循环
continue retry;
}
}
//8.到这里说明cas成功了
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//8.1 创建worker
final ReentrantLock mainLock = this.mainLock;
//这里会将firstTask传入新创建的线程中
w = new Worker(firstTask);
//w.thread是getThreadFactory().newThread(this);
//this指的是Worker
final Thread t = w.thread;
if (t != null) {
//8.2加独占锁,为了workers同步
//因为可能多个线程调用了线程池的execute方法。
// private final ReentrantLock mainLock = new ReentrantLock();
mainLock.lock();
try {
//8.3重新检查线程池状态,为了避免在获取锁前调用了shutdown接口
int c = ctl.get();
int rs = runStateOf(c);
//如果线程还在运行 或 线程是shutdown且firstTask是null
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//如果线程已经在运行抛出异常 防止重复启动
//一个线程重复启动2次会报java.lang.IllegalThreadStateException
if (t.isAlive())
throw new IllegalThreadStateException();
//8.4添加worker
workers.add(w);
int s = workers.size();
//largestPoolSize默认值是0
//这里是记录最大线程数有多少了
if (s > largestPoolSize){
largestPoolSize = s;
}
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//(8.5)添加成功则启动任务
if (workerAdded) {
//注意这里 t start的时候启动的到底是谁这个要弄清楚
//调用的是Worker的run方法哦
//Wroker的run方法如下
//runWorker(this);
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
//如果线程启动失败尝试终结该线程
addWorkerFailed(w);
}
//返回添加工作线程的启动状态
return workerStarted;
}
new Worker(Runnable firstTask)
当用户线程提交任务到线程池后,具体是使用 worker 来执行的,先看下 Worker 的构造函数:
Worker(Runnable firstTask) {
// 在调用runWorker前禁止中断
setState(-1);
this.firstTask = firstTask;
//使用线程工厂创建一个线程
this.thread = getThreadFactory().newThread(this);
}
runWorker
Worker实现了run方法,run方法启动的时候调用的是runWorker方法。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
//获取第一个task 即 创建该线程时关联的task
Runnable task = w.firstTask;
//清空firstTask 因为firstTask的引用已经给了task
w.firstTask = null;
//(9)status设置为0,允许中断
/***
不要考虑为什么先解锁 因为没有真正的加锁。
unlock的源码:
setExclusiveOwnerThread(null);
setState(0);
***/
w.unlock();
boolean completedAbruptly = true;
try {
//(10)
//第一次是task!=null 后面的都是getTask()!=null
//getTask会阻塞一直获取到任务为止
while (task != null || (task = getTask()) != null) {
//(10.1)
w.lock();
//如果当前线程已经被打断或者 线程池状态不对那么中断当前线程
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted()){
wt.interrupt();
}
try {
//是一个空实现,可以继承ThreadPoolExecutor 扩展实现哦
beforeExecute(wt, task);
Throwable thrown = null;
try {
//(10.3)执行任务
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 {
//(10.4)任务执行完毕后干一些事情
afterExecute(task, thrown);
}
} finally {
task = null;
//(10.5)统计当前worker完成了多少个任务
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//(11)当firstTask执行完成并且队列中的任务执行完毕 执行清理工作
processWorkerExit(w, completedAbruptly);
}
}
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);
// allowCoreThreadTimeOut 如果是true代表核心线程也要清除!
// 默认是false 即只清除空闲线程
// 如果 wc > corePoolSize即线程数量大于核心线程数 那么timed是true
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//如果总数大于核心线程数且 空队列
//总数减少1
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//这一步是关键,需要了解poll和take的区别,take会进行阻塞。
Runnable r = timed ?
//即如果线程数量大于核心线程数 直接使用poll超时获取方法
//如果在keepalive时间内,没有获取到任务,那么就会返回null,跳出循环
//执行processWorkerExit
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
//否则线程数量小于等于核心线程数使用阻塞获取任务的方式获取任务
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
poll 方法作用是移除并返回队列的头节点。但是如果当队列里面是空的,没有任何东西可以移除的时候,便会返回 null 作为提示。
带时间参数的 poll 方法:如果能够移除,便会立刻返回这个节点的内容;如果队列是空的就会进行等待,等待时间正是我们指定的时间,直到超时时间到了,如果队列里依然没有元素可供移除,便会返回 null 作为提示。
take 方法的作用是获取并移除队列的头结点。通常在队列里有数据的时候会正常取出数据并删除;但是如果执行 take 的时候队列里无数据,则阻塞,直到队列里有数据;一旦队列里有数据了,就会立刻解除阻塞状态,并且取到数据。
timed为true则标志着:允许核心线程超时被释放或者当前线程数超过核心线程数。一旦为tue,就会去使用阻塞队列的poll方法,如果keepAliveTime的时间里获取不到任务,就会返回Null,在上一级,也就是runWorker方法中去释放资源。
————————————————
版权声明:本文为CSDN博主「辉度」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/a17816876003/article/details/107682030
processWorkerExit
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//(11.1)统计整个线程池完成的任务个数,并从工作集里面删除当前woker
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
//(11.2)如果是下面的情况 尝试设置线程池状态为TERMINATED
//1.当前是shutdonw状态并且工作队列为空
//2.当前是stop状态当前线程池里面没有活动线程
tryTerminate();
//(11.3)如果当前线程个数小于核心个数,则增加
int c = ctl.get();
//TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
//allowCoreThreadTimeOut
//如果是true代表核心线程也要清除! 默认是false只清除空闲线程
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
//如果还有任务未执行 会保留一个线程
min = 1;
//总数大于等于核心线程数 直接返回
if (workerCountOf(c) >= min)
//这里清除的是空闲线程
return;
}
//总数小于核心线程数 添加一个核心线程
//判断当前线程池里面线程个数是否小于核心线程个数,如果是则新增一个线程。
addWorker(null, false);
}
}
如上代码(11.1)统计线程池完成任务个数,可知在统计前加了全局锁,把当前工作线程中完成的任务累加到全局计数器,然后从工作集中删除当前 worker。
代码(11.2)判断如果当前线程池状态是 shutdonw 状态并且工作队列为空或者当前是 stop 状态当前线程池里面没有活动线程则设置线程池状态为 TERMINATED,如果设置为了 TERMINATED 状态还需要调用条件变量 termination 的 signalAll() 方法激活所有因为调用线程池的 awaitTermination 方法而被阻塞的线程
代码(11.3)则判断当前线程里面线程个数是否小于核心线程个数,如果是则新增一个线程。
tryTerminate
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
if (workerCountOf(c) != 0) {
// Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
//空实现 可以继承ThreadPoolExecutor 扩展实现哦
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
关闭线程池
shutdown: 1、调用之后不允许继续往线程池内继续添加线程; 2、线程池的状态变为SHUTDOWN状态; 3、所有在调用shutdown()方法之前提交到ExecutorSrvice的任务都会执行; 4、一旦所有线程结束执行当前任务,ExecutorService才会真正关闭。
shutdownNow(): 1、该方法返回尚未执行的 task 的 List; 2、线程池的状态变为STOP状态; 3、阻止所有正在等待启动的任务, 并且停止当前正在执行的任务。
简单点来说,就是: shutdown()调用后,不可以再 submit 新的 task,已经 submit 的将继续执行 shutdownNow()调用后,试图停止当前正在执行的 task,并返回尚未执行的 task 的 list
注意是试图
———————————————— 版权声明:本文为CSDN博主「老周聊架构」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/riemann_/ar…
shutdown
shutdown 操作:把线程池的状态设置成SHUTDOWN状态,然后中断所有没有正执行任务的线程
调用 shutdown 后,线程池就不会在接受新的任务了,但是工作队列里面的任务还是要执行的,该方法立刻返回的,并不等待队列任务完成在返回。代码如下:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//检查如果设置了安全管理器,则看当前调用 shutdown 命令的线程是否有关闭线程的权限,如果有权限则还要看调用线程是否有中断工作线程的权限,如果没有权限则抛出 `SecurityException` 或者 `NullPointerException` 异常
//(12)modifyThread权限检查
checkShutdownAccess();
//(13)设置当前线程池状态为SHUTDOWN,如果已经是SHUTDOWN则直接返回
advanceRunState(SHUTDOWN);
//(14)设置中断标志 遍历workers 判断后 依次打断
//调用的是 interruptIdleWorkers(false);
interruptIdleWorkers();
//该方法是空实现 但是不能重写 因为他是默认修饰符修饰
//ScheduledThreadPoolExecutor 实现了该方法
onShutdown();
} finally {
mainLock.unlock();
}
//(15)尝试状态变为TERMINATED
tryTerminate();
}
//如果当前状态 >= SHUTDOWN 则直接返回,否者设置当前状态为 SHUTDOWN。
private void advanceRunState(int targetState) {
for (;;) {
int c = ctl.get();
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
interruptIdleWorkers
//,设置所有空闲线程的中断标志,这里首先加了全局锁,同时只有一个线程可以调用 shutdown 设置中断标志,然后尝试获取 worker 自己的锁,获取成功则设置中断标识,由于正在执行的任务已经获取了锁,所以正在执行的任务没有被中断。这里中断的是阻塞到 `getTask()` 方法,企图从队列里面获取任务的线程,也就是空闲线程。
//这里的only one是false
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
//注意这里和shutDownNow的判断条件不同
//如果工作线程正在工作,并且没有正在运行则设置中断标记 注意是设置中断标记
if (!t.isInterrupted() && w.tryLock()) {
try {
//interrupt方法:如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出 InterruptedException,并清除打断标记置为false;
//如果打断正在运行的线程,则会设置打断标记true;park 的线程被打断,也会设置打断标记true。即isInterrupted是true。
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
代码(15)判断如果当前线程池状态是 shutdonw 状态并且工作队列为空或者当前是 stop 状态当前线程池里面没有活动线程则设置线程池状态为 TERMINATED,如果设置为了 TERMINATED 状态还需要调用条件变量 termination 的 signalAll()方法激活所有因为调用线程池的 awaitTermination 方法而被阻塞的线程
tryTerminate
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
if (workerCountOf(c) != 0) {
// Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
//空实现 可以继承ThreadPoolExecutor 扩展实现哦
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
shutdownNow
调用 shutdownNow 后,线程池就不会在接受新的任务了,并且丢弃工作队列里面里面的任务,正在执行的任务会被中断,该方法是立刻返回的,并不等待激活的任务执行完成在返回。返回值为这时候队列里面被丢弃的任务列表。代码如下:
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();//(16)权限检查
advanceRunState(STOP);//(17) 设置线程池状态为stop
interruptWorkers();//(18)中断所有线程
tasks = drainQueue();//(19)移动队列任务到tasks
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
如上代码首先调用代码(16)检查权限,然后调用代码(17)设置当前线程池状态为 stop,然后执行代码(18)中断所有的工作线程,这里需要注意的是中断所有的线程,包含空闲线程和正在执行任务的线程,代码如下:
interruptWorkers
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
void interruptIfStarted() {
Thread t;
//注意这里和shutDown的判断条件不同
//如果线程池的状态大于等0 且 当前线程不是空 且打断标记是false
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
awaitTermination
当线程调用 awaitTermination 方法后,当前线程会被阻塞,知道线程池状态变为了 TERMINATED 才返回,或者等待时间超时才返回,整个过程独占锁,代码如下:
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (;;) {
if (runStateAtLeast(ctl.get(), TERMINATED))
return true;
if (nanos <= 0)
return false;
nanos = termination.awaitNanos(nanos);
}
} finally {
mainLock.unlock();
}
}
如上代码首先获取了独占锁,然后无限循环内部首先判断当前线程池状态是否至少是 TERMINATED 状态,如果是则直接返回。否者说明当前线程池里面还有线程在执行,则看设置的超时时间 nanos 是否小于 0,小于 0 则说明不需要等待,则直接返回;如果大于0则调用条件变量 termination 的 awaitNanos 方法等待 nanos 时间,期望在这段时间内线程池状态内变为 TERMINATED 状态。
在讲解 shutdown 方法时候提到当线程池状态变为 TERMINATED 后,会调用 termination.signalAll() 用来激活调用条件变量 termination 的 await 系列方法被阻塞的所有线程,所以如果在调用了 awaitTermination 之后调用了 shutdown 方法,并且 shutdown 内部设置线程池状态为 TERMINATED 了,则 termination.awaitNanos 方法会返回。
另外在工作线程 Worker 的 runWorker 方法内当工作线程运行结束后,会调用 processWorkerExit 方法,processWorkerExit 方法内部也会调用 tryTerminate 方法测试当前是否应该把线程池设置为 TERMINATED 状态,如果是,则也会调用 termination.signalAll() 用来激活调用线程池的 awaitTermination 方法而被阻塞的线程
另外当等待时间超时后,termination.awaitNanos 也会返回,这时候会重新检查当前线程池状态是否为 TERMINATED,如果是则直接返回,否者继续阻塞挂起自己。
总结
2处执行拒绝策略
1.线程池不在运行状态
2.线程池中的阻塞队列已满且创建救急线程失败
3个地方addworke
1.添加核心线程
2.添加救急线程
3.当一个线程退出的时候发现当前线程数小于核心线程
正确处理执行任务异常
方式1-主动捕获
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(() -> {
try {
log.debug("task1");
int i = 1 / 0;
} catch (Exception e) {
log.error("error:", e);
}
});
输出:
21:59:04.558 c.TestTimer [pool-1-thread-1] - task1
21:59:04.562 c.TestTimer [pool-1-thread-1] - error:
java.lang.ArithmeticException: / by zero
at cn.itcast.n8.TestTimer.lambda$main$0(TestTimer.java:28)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
方式2-Future
package 线程池;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class MyFutureTask {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(1);
Future<Boolean> f = pool.submit(() -> {
System.out.println("task1");
int i = 1 / 0;
return true;
});
System.out.println("result: " + f.get());
}
}
以下内容是f.get():
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at com.dtyunxi.haier.center.account.api.dto.test.ThreadPool.main(Main.java:15)
Caused by: java.lang.ArithmeticException: / by zero
at com.dtyunxi.haier.center.account.api.dto.test.ThreadPool.lambda$main$0(Main.java:12)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
任务调度线程池
在『任务调度线程池』功能加入之前,可以使用 java.util.Timer 来实现定时功能,Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。
Timer
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task1 = new TimerTask() {
@Override
public void run() {
log.debug("task 1");
sleep(2);
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
log.debug("task 2");
}
};
// 使用 timer 添加两个任务,希望它们都在 1s 后执行
// 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此『任务1』的延时,影响了『任务2』的执行
timer.schedule(task1, 1000);
timer.schedule(task2, 1000);
}
输出
20:46:09.444 c.TestTimer [main] - start...
20:46:10.447 c.TestTimer [Timer-0] - task 1
20:46:12.448 c.TestTimer [Timer-0] - task 2
ScheduledExecutorService
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
// 添加两个任务,希望它们都在 1s 后执行
executor.schedule(() -> {
System.out.println("任务1,执行时间:" + new Date());
try { Thread.sleep(2000); } catch (InterruptedException e) { }
}, 1000, TimeUnit.MILLISECONDS);
executor.schedule(() -> {
System.out.println("任务2,执行时间:" + new Date());
}, 1000, TimeUnit.MILLISECONDS);
输出
任务1,执行时间:Thu Jan 03 12:45:17 CST 2019
任务2,执行时间:Thu Jan 03 12:45:17 CST 2019
scheduleAtFixedRate
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
log.debug("start...");
pool.scheduleAtFixedRate(() -> {
log.debug("running...");
}, 1, 1, TimeUnit.SECONDS);
输出
21:45:43.167 c.TestTimer [main] - start...
21:45:44.215 c.TestTimer [pool-1-thread-1] - running...
21:45:45.215 c.TestTimer [pool-1-thread-1] - running...
21:45:46.215 c.TestTimer [pool-1-thread-1] - running...
21:45:47.215 c.TestTimer [pool-1-thread-1] - running...
任务执行时间超过了间隔时间
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
log.debug("start...");
pool.scheduleAtFixedRate(() -> {
log.debug("running...");
sleep(2);
}, 1, 1, TimeUnit.SECONDS);
一开始,延时 1s,接下来,由于任务执行时间 > 间隔时间,间隔被『撑』到了 2s
输出:
21:44:30.311 c.TestTimer [main] - start...
21:44:31.360 c.TestTimer [pool-1-thread-1] - running...
21:44:33.361 c.TestTimer [pool-1-thread-1] - running...
21:44:35.362 c.TestTimer [pool-1-thread-1] - running...
21:44:37.362 c.TestTimer [pool-1-thread-1] - running...
scheduleWithFixedDelay
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
log.debug("start...");
pool.scheduleWithFixedDelay(()-> {
log.debug("running...");
sleep(2);
}, 1, 1, TimeUnit.SECONDS);
一开始,延时 1s,scheduleWithFixedDelay 的间隔是 上一个任务结束 <-> 延时 <-> 下一个任务开始 所以间隔都是 3s。
输出:
21:40:55.078 c.TestTimer [main] - start...
21:40:56.140 c.TestTimer [pool-1-thread-1] - running...
21:40:59.143 c.TestTimer [pool-1-thread-1] - running...
21:41:02.145 c.TestTimer [pool-1-thread-1] - running...
21:41:05.147 c.TestTimer [pool-1-thread-1] - running...
评价 整个线程池表现为:线程数固定,任务数多于线程数时,会放入无界队列排队。任务执行完毕,这些线程也不会被释放。用来执行延迟或反复执行的任务。
模式之 Worker Thread
定义
让有限的工作线程(Worker Thread)来轮流异步处理无限多的任务。
也可以将其归类为分工模式,它的典型实现就是线程池,也体现了经典设计模式中的享元模式。
例如,海底捞的服务员(线程),轮流处理每位客人的点餐(任务),如果为每位客人都配一名专属的服务员,那么成本就太高了(对比另一种多线程设计模式:Thread-Per-Message 为每一个请求新分配一个线程,由这个线程来执行处理。)
注意,不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率。队列也是这样哈。
例如,如果一个餐馆的工人既要招呼客人(任务类型A),又要到后厨做菜(任务类型B)显然效率不咋地,分成服务员(线程池A)与厨师(线程池B)更为合理,当然你可以更细致的分工。
饥饿
固定大小线程池会有饥饿现象
两个工人是同一个线程池中的两个线程
他们要做的事情是:为客人点餐和到后厨做菜,这是两个阶段的工作
客人点餐:必须先点完餐,等菜做好,上菜,在此期间处理点餐的工人必须等待。
后厨做菜:没啥说的,做就是了。
比如工人A 处理了点餐任务,接下来它要等着 工人B 把菜做好,然后上菜,他俩也配合的蛮好
但现在同时来了两个客人,这个时候工人A 和工人B 都去处理点餐了,这时没人做饭了,这就是饥饿
public class TestDeadLock {
static final List<String> MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅");
static Random RANDOM = new Random();
static String cooking() {
return MENU.get(RANDOM.nextInt(MENU.size()));
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(() -> {
log.debug("处理点餐...");
Future<String> f = executorService.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
/*
executorService.execute(() -> {
log.debug("处理点餐...");
Future<String> f = executorService.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
*/
}
}
根治的方法是让等待时间过长的线程有重新获取锁的机会,可以给每一个等待的线程一个超时时间,超过某一时间后可以重新获取一次锁,线程在获取锁的过程中加一个带有超时时间、自旋间隔的自旋逻辑。