Java线程池ThreadPoolExecutor

3 篇文章 0 订阅
3 篇文章 0 订阅

线程池ThreadPoolExecutor

1、类关系

在这里插入图片描述

  • Executor 是一个接口, 它是 Executor 框架的基础, 它将任务的提交与任务的执行分离开来。
  • ExecutorService 接口继承了 Executor【原来接口是可以继承接口的】, 在其上做了一些shutdown()、 submit()的扩展, 可以说是真正的线程池接口。
  • AbstractExecutorService 抽象类实现了 ExecutorService 接口中的大部分方法。
  • ThreadPoolExecutor 是线程池的核心实现类, 用来执行被提交的任务。

2、构造函数参数解析

其构造函数有多个,我们来详解参数最多的那个
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) { … }

corePoolSize:核心线程数

线程池维护的最小线程数量,即核心线程数。

maximumPoolSize:最大线程数

线程池允许创建的最大线程数量。

keepAliveTime:空闲线程存活时间

当一个【可被回收的线程】的空闲时间大于keepAliveTime,就会被回收。

  • 什么是可被回收的线程?
    1、设置allowCoreThreadTimeout=true的核心线程
    2、大于核心线程数的线程(非核心线程)

unit:keepAliveTime的时间单位

workQueue:工作队列或阻塞队列

BlockingQueue workQueue 是阻塞队列接口,它用于存放执行的任务:
当提交的任务数大于corePoolSize,再提交的任务就存放在阻塞队列,任务调度时再从队列中取出任务。它仅仅用来存放被execute()方法提交的Runnable任务。阻塞队列实现了BlockingQueue接口。

阻塞队列是有容量的,其容量取决于 workQueue 实现的是哪一种。
有界的阻塞队列规定了最大容量,无界的没有规定容量。

注意:阻塞队列的容量的含义与corePoolSize、maximumPoolSize区分开。

阻塞队列常用于生产者和消费者的场景, 生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。 阻塞队列就是生产者用来存放元素、 消费者用来获取元素的容器。

JDK常用的5个阻塞队列,都实现了 BlockingQueue 接口, 也都是线程安全的:
· ArrayBlockingQueue: 一个由数组结构组成的有界阻塞队列。
· LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列。
· PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列。
· DelayQueue: 一个使用优先级队列实现的无界阻塞队列。
· SynchronousQueue: 一个不存储元素的阻塞队列。

BlockingQueue的基本方法:

插入描述及特点
add(E e)插入元素。当队列已满,此时 add 会抛出异常
offer(E e)插入成功会返回true
put(E e)当队列已满,此时 put, 队列会一直阻塞生产者线程, 直到队列可用或者响应中断退出
移除/取出描述及特点
remove()直接移除,没有返回值。当队列为空,此时 remove 会抛出异常
poll()取出一个元素,如果没有返回null
take()当队列为空, 如果消费者线程从队列里 take 元素, 队列会阻塞消费者线程, 直到队列不为空

threadFactory:线程工厂

创建线程的工厂,可以设定线程名、线程编号等。

handler:拒绝策略

当核心线程数已满,阻塞队列已满时,新提交的任务使用拒绝策略处理。可以自定义拒绝策略,拒绝策略需要实现 RejectedExecutionHandler 接口。

ThreadPoolExecutor里提供的拒绝策略有四种,以下为4个内部类的源码。

  1. AbortPolicy:丢弃任务,并抛出异常。(默认)
public static class AbortPolicy implements RejectedExecutionHandler {
    public AbortPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    	// 只抛出异常
        throw new RejectedExecutionException(...);
    }
}
  1. DiscardPolicy:丢弃任务,但是不抛出异常。
public static class DiscardPolicy implements RejectedExecutionHandler {
    public DiscardPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    	// 空实现
    }
}
  1. 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); // 执行当前任务
        }
    }
}
  1. CallerRunsPolicy:由调用线程自行处理该任务。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
     public CallerRunsPolicy() { }
     public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
         if (!e.isShutdown()) {
             r.run();
         }
     }
 }

策略没有好坏,各有不同的应用场景。

3、提交任务

  • execute():提交不需要返回值的任务, 所以无法判断任务是否被线程池执行成功。
  • submit():提交需要返回值的任务。 线程池会返回一个 future 类型的对象, 通过这个 future 对象可以判断任务是否执行成功, 并且可以通过 future的 get()方法来获取返回值, get()方法会阻塞当前线程直到任务完成, 而使用 get(long timeout, TimeUnit unit) 方法则会阻塞当前线程一段时间后立即返回, 这时候有可能任务没有执行完。

4、关闭线程池

shutdown() 或 shutdownNow() 。
它们的原理是遍历线程池中的工作线程, 然后逐个调用线程的 interrupt 方法来中断线程, 所以无法响应中断的任务可能永远无法终止。

  • isShutdown():调用这两个关闭方法中的任意一个,就会返回 true。
  • isTerminaed ():当所有的任务都已关闭后, 才表示线程池关闭成功,才会返回 true。

通常调用 shutdown 方法来关闭线程池, 如果任务不一定要执行完,则可以调用 shutdownNow 方法

5、线程池的执行流程

在这里插入图片描述
在这里插入图片描述
对比上面两个图,可以把执行流程大致分为4种情况:(假设阻塞队列设置了容量)

  1. 线程池刚创建时,调用 execute() 方法添加一个任务,此时正在运行的线程数量 < corePoolSize,则马上创建新线程并运行这个任务。

  2. 调用 execute() 方法添加一个任务,此时核心线程数已满,阻塞队列未满,那么此任务就添加到阻塞队列,任务调度时再从队列中取出。

  3. 调用 execute() 方法添加一个任务,此时核心线程数已满,阻塞队列已满,累计提交任务数 < maximumPoolSize,那么此时也会创建新线程并运行这个任务。

  4. 调用 execute() 方法添加一个任务,此时核心线程数已满,阻塞队列已满,累计提交任务数 > maximumPoolSize,新提交的任务将按照拒绝策略处理。

6、线程池代码实例

	int corePoolSize = 5;
	int maximumPoolSize = 5;
	int queueSize = 2;
	RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy(); 	// 拒绝策略用的是直接丢弃
	ThreadPoolExecutor poor = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), handler);
	for (int i = 0; i < 50; i++) {
		final int index = i;
		poor.execute(new Runnable() {
			@Override
			public void run() {
				try {
					System.out.println("第" + index + "个线程正在执行");
				} catch (Exception e) {
					System.out.println("异常: " + e.getMessage());
				}
			}
		});
	}

核心线程数和最大线程数都是5,阻塞队列长度是2,运行结果:

第0个线程正在执行
第5个线程正在执行
第2个线程正在执行
第6个线程正在执行
第3个线程正在执行
第1个线程正在执行
第4个线程正在执行

只执行了7个线程,其余任务均被丢弃。大家可以自行修改线程参数体会。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值