ThreadPoolExecutor线程池详解

1、基本概念

ThreadPoolExecutor类实现了ExecutorService接口和Executor接口,可以设置线程池corePoolSize,最大线程池大小,BlockingQueue,AliveTime,拒绝策略等。

常用构造方法

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
	....
}
参数含义
corePoolSize线程池核心线程数量。在创建线程池后,默认情况下线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用prestartAllCoreThreads( )或prestartCoreThread( )方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程
maximumPoolSize线程池最大线程数,它表示在线程池中最多能创建多少个线程;
keepAliveTime当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,传入true,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;该方法你就理解成核心线程是否超时回收
unit线程所允许的空闲时间单位
workQueue线程池所使用的缓冲队列,BlockingQueue接口的具体实例
threadFactory线程工厂,主要用来创建线程,ThreadFactory是一个接口,只有一个方法。通过线程工厂可以对线程的一些属性进行定制,通常在实际开发中都会定义自己的线程工厂,指定有意义的线程名称,方便出问题的时候回溯
handler线程池对拒绝任务的处理策略

2、缓冲队列BlockingQueue

类型说明
ArrayBlockingQueue是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序
LinkedBlockingQueue一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue,静态工厂方法Executors.newFixedThreadPool()使用了这个队列
SynchronousQueue一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
PriorityBlockingQueue一个具有优先级的无限阻塞队列

3、饱和策略RejectedExecutionHandler

类型说明
AbortPolicy新的任务将被丢弃,并抛出异常(线程池默认饱和策略)
DiscardPolicy新的任务将被丢弃,不会抛出异常
CallerRunsPolicy由调用者所在线程来运行当前任务
DiscardOldestPolicy丢弃队列最前面的任务,然后重新提交当前任务
也可以根据应用场景需要实现RejectedExecutionHandler接口,实现自己的自定义策略,来满足如记录日志、持久化不能处理的任务等需求。

4、线程池的工作方式

当新提交一个任务时:
(1)、线程数量小于corePoolSize,即便线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
(2)、线程池中的线程数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。
(3)、线程池中的线程数量大于corePoolSize,缓冲队列workQueue已满,并且线程池中的线程数量小于maximumPoolSize,则创建新的线程来处理被添加的任务。
(4)、线程池中的线程数量大于corePoolSize,缓冲队列workQueue已满,并且线程池中的线程数量等于maximumPoolSize,那么通过handler所指定的策略来处理此任务。

处理任务的优先级为:
核心线程corePoolSize --> 任务队列workQueue --> 最大线程maximumPoolSize --> 使用handler饱和策略

在这里插入图片描述

4.1 核心线程数是如何保持不销毁的?

查看ThreadPoolExecutor.getTask()方法

	/**
     * Performs blocking or timed wait for a task, depending on
     * current configuration settings, or returns null if this worker
     * must exit because of any of:
     * 1. There are more than maximumPoolSize workers (due to
     *    a call to setMaximumPoolSize).
     * 2. The pool is stopped.
     * 3. The pool is shutdown and the queue is empty.
     * 4. This worker timed out waiting for a task, and timed-out
     *    workers are subject to termination (that is,
     *    {@code allowCoreThreadTimeOut || workerCount > corePoolSize})
     *    both before and after the timed wait, and if the queue is
     *    non-empty, this worker is not the last thread in the pool.
     *
     * @return task, or null if the worker must exit, in which case
     *         workerCount is decremented
     */
    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?
		// 无限死循环
        for (;;) {
            int c = ctl.get();
            // 获取当前线程状态
            int rs = runStateOf(c);
            // 满足一定条件的情况下,检查任务队列是否为空?
            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 {
                //关键代码,以何种方式获取BlockingQueue<Runnable>中的对象,非阻塞 Or 阻塞?
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

是否保留核心线程,其中关键的代码是进入无限循环后,跳出循环的条件是怎么样的
timed条件为true的情况下,使用Queue的poll()方法,如果获取不到直接return null,此时会退出循环
timed条件为false的情况下,使用Queue的take()方法,如果获取不到则进入阻塞状态,直到获取到任务

Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
  • poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则等待time参数规定的时间,仍取不到时返回null
  • take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,进入阻塞状态直到获取到新的队列对象为止

5、如何合理配置最大线程数

  • CPU密集型
    定义:CPU密集型的意思就是该任务需要大量运算,没有阻塞,CPU一直全速运行。
    CPU密集型任务只有在真正的多核CPU上才可能得到加速(通过多线程),单核多线程则意义不大
    CPU密集型任务配置尽可能少的线程数。

    线程数 = CPU核数 + 1

  • IO密集型
    定义:IO密集型,即该任务需要大量的IO,即大量的阻塞。
    在单线程上运行IO密集型任务,会浪费大量的CPU运算能力在等待IO上。
    所以IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要利用了被浪费掉的阻塞时间。

    • 第一种配置方式:
      由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程。

      线程数 = CPU核数 * 2

    • 第二种配置方式:
      IO密集型时,大部分线程都阻塞,故需要多配置线程数。

      线程数 = CPU核数 / (1 – 阻塞系数(0.8~0.9之间))
      例如: 8核 / (1 – 0.9) = 80个线程数

6、代码演示

核心线程数:3,最大线程数:5,缓冲队列:10,饱和策略:AbortPolicy

package com.wzl.executor;

/**
 * 工作类
 */
public class Worker implements Runnable {

    int id;

    public Worker(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        System.out.println("Index: " + id + " is running , Thread Name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(6000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package com.wzl.executor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Test {

    private static final Logger log = LoggerFactory.getLogger(Test.class);

    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                3,
                5,
                1 * 60L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(10),
                new ThreadPoolExecutor.AbortPolicy()//饱和策略,AbortPolicy:任务队列满后,新的任务将被丢弃,并抛出异常
        );

        for (int i = 0; i < 100; i++) {
            try {
                threadPoolExecutor.execute(new Worker(i));
            } catch (Exception e) {
                log.error("发生异常,当前index:" + i, e);
                break;
            }
        }

        threadPoolExecutor.shutdown();
    }

}

运行结果 & 解答

  • Index 0、1、2 交给corePoolSize,正在执行
  • Index 3 ~ 12 进入缓冲队列(size == 10)进行等待
  • Index 13、14 交给maximumPoolSize扩展的2个线程进行处理,正在执行
  • Index 15 基于饱和策略,进行处理,这里是拒绝任务,抛出异常
  • Index 3 ~ 12 等待的任务,在有空闲线程后进行处理

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值