Java 线程池 ThreadPoolExecutor

0,Java 线程状态转换

在这里插入图片描述

1,Java 线程池的三种创建方式

  • newCacheThreadPool():核心线程数是 0,非核心线程数是 2^31 - 1,没有阻塞队列(不存放任务)
    • 适合任务数比较密集,但每个任务执行时间较短的情况
  • newFixedThreadPool(n):核心线程数是 n,没有非核心线程,阻塞队列最大为 2^31 - 1
    • 适用于任务量已知,相对耗时的任务
  • newSingleThreadExecutor():核心线程数是 1,没有非核心线程,阻塞队列最大为 2^31 - 1
    • 适用于多个任务排队执行
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(
    		0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
			new SynchronousQueue<Runnable>());
}

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(
    		nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
			new LinkedBlockingQueue<Runnable>());
}

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
			new LinkedBlockingQueue<Runnable>()));
}

可以看到以上三种创建方式实际使用的都是 ThreadPoolExecutor 类,只是参数不同而已。

2,ThreadPoolExecutor 类的原理

在这里插入图片描述

  • ThreadPoolExecutor:基本的线程池实现
  • ScheduledThreadPoolExecutor:任务调度线程池:带有定时任务的线程池
    • 在『任务调度线程池』功能加入之前,可以使用 java.util.Timer 来实现定时功能,Timer 的优点在于简单易用
    • 但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务

线程池有五种状态:

  • RUNNING:正常运行状态,可接收新任务,可处理阻塞队列中的任务
  • SHUTDOWN:不会接收新任务,但会处理阻塞队列剩余任务
  • STOP:会中断正在执行的任务,并抛弃阻塞队列任务
  • TIDYING:任务全执行完毕,活动线程为 0,即将进入终结
  • TERMINATED:终结状态
1,构造方法及参数含义

ThreadPoolExecutor 类位于 java.uitl.concurrent 包中,其参数含义如下:

public ThreadPoolExecutor(
		int corePoolSize,					// 核心线程数,核心线程就是一直存在的线程
		
        int maximumPoolSize,				// 最大线程数,表示线程池中最多能创建多少个线程
        									// 非核心线程数 = 最大线程数 - 核心线程数
        									
        long keepAliveTime,					// 针对非核心线程而言,表示线程没有任务执行时最多保持多久时间会终止
			// 默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用
			// 		当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到 keepAliveTime,
			// 		则会终止,直到线程池中的线程数不超过corePoolSize
			// 但是如果调用了 allowCoreThreadTimeOut(boolean) 方法
			// 		在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用
			// 		直到线程池中的线程数为 0
        TimeUnit unit,						// 时间单位,与 keepAliveTime 配合使用,针对非核心线程
        
        BlockingQueue<Runnable> workQueue,	// 存放任务的阻塞队列
        
        ThreadFactory threadFactory,		// 创建线程的工厂,可以为线程创建时起个好名字
        
        RejectedExecutionHandler handler	// 拒绝策略
        									// 任务太多的时候会进行拒绝操作
        									// 核心线程,非核心线程,任务队列都放不下时
)

unit 参数有7种取值,在 TimeUni t类中有7种静态属性:

TimeUnit.DAYS;              //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒

workQueue 参数表示一个阻塞队列,用来存储等待执行的任务,有以下几种选择:

  • ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小
  • LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为 Integer.MAX_VALUE
  • SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务

handler 参数表示拒绝策略,当线程池的任务缓存队列已满,并且线程池中的线程数目达到 maximumPoolSize,如果还有任务到来就会采取任务拒绝策略。

在这里插入图片描述

handler 通常有以下四种策略:

  • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常(这是默认策略)
  • ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但不抛出异常
  • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
  • ThreadPoolExecutor.CallerRunsPolicy:由调用线程(调用者)处理该任务
2,一些重要方法

ThreadPoolExecutor 类中的几个重要方法:

  • execute():向线程池提交一个任务,交由线程池去执行
  • submit():也是向线程池提交任务,但是和execute()方法不同,它能够返回任务执行的结果
    • 它实际上还是调用的 execute() 方法,只不过它利用了 Future 来获取任务执行结果
  • invokeAll():提交一个任务集合
  • invokeAny(): 提交一个任务集合,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
  • shutdown():关闭线程池,再也不会接受新的任务
    • 不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止
  • shutdownNow():关闭线程池,再也不会接受新的任务
    • 立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
  • isShutdown():不在 RUNNING 状态的线程池,此方法就返回 true
  • isTerminated():线程池状态是否是 TERMINATED

动态调整线程池的大小:

  • setCorePoolSize:设置 corePoolSize
  • setMaximumPoolSize:设置 maximumPoolSize

还有一些方法:

  • getQueue()
  • getPoolSize()
  • getActiveCount()
  • getCompletedTaskCount()
3,线程池状态

ThreadPoolExecutor 中定义了一个 volatile 变量,另外定义了几个 static final变量表示线程池的各个状态:

volatile int runState;				// 当前线程池的状态
static final int RUNNING    = 0;
static final int SHUTDOWN   = 1;
static final int STOP       = 2;
static final int TERMINATED = 3;

线程池的状态变化:

  • 当创建线程池后,线程池处于 RUNNING 状态
  • 当调用了 shutdown() 方法,则线程池处于 SHUTDOWN 状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕
  • 当调用了 shutdownNow() 方法,则线程池处于 STOP 状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务
  • 当线程池处于 SHUTDOWNSTOP 状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为 TERMINATED 状态
4,线程池模型

ThreadPoolExecutor 类构造出来的线程池的模型如下:

在这里插入图片描述

大体上由三大部分组成:

  • 核心线程:一直存在的线程,数量为 corePoolSize
    • 在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务;当线程池中的线程数目达到 corePoolSize 后,就会把到达的任务放到缓存队列当中;
    • 除非手动调用了 prestartAllCoreThreads() 或者 prestartCoreThread() 方法,来预创建线程,即在没有任务到来之前就创建线程。
  • 非核心线程:临时创建的线程,会根据任务的多少来进行创建
    • 当然总的非核心线程的数量不能大于 maximumPoolSize - corePoolSize
    • 如果总的非核心线程的数量很大,并且任务非常多,就会创建非常多的线程
  • 任务队列
    • SynchronousQueue:只能存放一个任务
    • LinkedBlockingQueue:可以无限存放任务,如果任务超级多,会有内存溢出的可能

同一时刻,线程池中所能接纳最大任务数为:maximumPoolSize + 任务队列的长度
当超出这个范围后,如果再有新的任务过来,将会被拒绝。

线程池中的线程的初始化:

  • 默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程
  • 在实际中,如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
    • prestartCoreThread():初始化一个核心线程
    • prestartAllCoreThreads():初始化所有核心线程

处理任务的流程:

  • 如果当前线程池中的线程数目小于 corePoolSize,每来一个任务,就会创建一个线程去执行这个任务
  • 如果当前线程池中的线程数目>=corePoolSize,每来一个任务,会尝试将其添加到任务缓存队列当中
    • 若添加成功,则该任务会等待空闲线程将其取出去执行
    • 若添加失败(一般来说是任务缓存队列已满),则会尝试创建临时线程去执行这个任务
  • 核心线程,任务队列,非核心线程都使用完后,如果还有新的任务过来,将会进行拒绝处理

线程复用: 当线程处理完已分配的任务后,在没有销毁之前,还会用于去处理新的任务。这样可以避免创建过多的线程。

3,任务的执行过程

源码部分是 ThreadPoolExecutor 类中的 execute 方法:

在这里插入图片描述

流程图如下:

在这里插入图片描述


在这里插入图片描述

4,合理设置线程池的大小

一般需要根据任务的类型来配置线程池大小:

  • 如果是 CPU密集型任务,就需要尽量压榨 CPU,可以设为 CPU个数+1
  • 如果是 IO密集型任务,可以设置为 CPU个数*2

这只是一个参考值,具体的设置还需要根据实际情况进行调整,可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值