线程池详解,带你全面了解线程池

线程池的工作原理
  1. 如果当前的线程个数比核心池个数小,当任务到来,会优先创建一个新的线程并执行任务
  2. 当已经到达核心池大小,则把任务放入队列,为了资源不被耗尽,队列的最大容量可能也是有上限的
  3. 如果达到队列上限则考虑继续创建新线程执行任务,如果此刻线程的个数已经到达最大池上限,则考虑把任务丢弃。

在这里插入图片描述

ThreadPoolExecutor 的实现
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
}

corePoolSize - 核心池大小。在初创建线程池时线程不会立即启动,直到有任务提交才开始启动线程并逐渐使线程数目达到corePoolSize。若想一开始就创建所有核心线程需调用prestartAllCoreThreads方法

maximumPoolSize -池中允许的最大线程数。当核心线程满且阻塞队列也满时才会判断当前线程数是否小于最大线程数,并决定是否创建新线程

keepAliveTime - 当线程数大于核心时,多于的空闲线程最多存活时间

unit - keepAliveTime 参数的时间单位:TimeUnit.SECONDS

workQueue - 当线程数目超过核心线程数时用于保存任务的队列。主要有3种类型的BlockingQueue可供选择:无界队列,有界队列和同步移交

threadFactory - 执行程序创建新线程时使用的工厂

handler - 阻塞队列已满且线程数达到最大值时所采取的拒绝策略。java默认提供了4种拒绝策略的实现方式:中止、抛弃、抛弃最旧的、调用者运行

阻塞队列BlockingQueue详解
  1. 如果运行的线程少于corePoolSize,则Executor始终首选添加新的线程,而不进行排队
  2. 如果运行的线程大于等于corePoolSize,则Executor始终首选将请求加入队列,而不添加新的线程
  3. 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝

主要有3种类型的BlockingQueue:

无界队列: LinkedBlockingQueue,newFixedThreadPool 采用的就是无界队列,采用无界队列最大的问题就是当 QPS 很高,发送数据很大,大量的任务被添加到无界队列里面,很容易内存溢出

有界队列: 一类是遵循FIFO原则的队列如ArrayBlockingQueue与有界的LinkedBlockingQueue,另一类是优先级队列如PriorityBlockingQueue由任务的Comparator决定;使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量

同步移交队列: 如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。

可选择的拒绝策略RejectedExecutionHandler详解
  1. AbortPolicy 中止策略:默认拒绝策略
// 使用该策略时在饱和时会抛出RejectedExecutionException
// 继承自RuntimeException
// 调用者可捕获该异常自行处理
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
}
  1. DiscardPolicy 抛弃策略
// 如代码所示,不做任何处理直接抛弃任务
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
}
  1. DiscardOldestPolicy 抛弃旧任务策略
// 先将阻塞队列中的头元素出队抛弃,再尝试提交任务
// 如果此时阻塞队列使用PriorityBlockingQueue优先级队列
// 将会导致优先级最高的任务被抛弃,因此不建议将该种策略配合优先级队列使用
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        e.getQueue().poll();
        e.execute(r);
    }
}
  1. CallerRunsPolicy 调用者运行策略
// 既不抛弃任务也不抛出异常,直接运行任务的run方法,换言之将任务回退给调用者来直接运行
// 使用该策略时线程池饱和后将由调用线程池的主线程自己来执行任务
// 因此在执行任务的这段时间里主线程无法再提交新任务
// 从而使线程池中工作线程有时间将正在处理的任务处理完成
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        r.run();
    }
}
Java提供的四种常用线程池解析
  1. CachedThreadPool
// 内部实现是使用了SynchronousQueue队列
// 在newCachedThreadPool中如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}
  1. FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}
  1. ScheduledThreadPool
// ScheduledThreadPoolExecutor的父类即ThreadPoolExecutor
// DelayedWorkQueue是一个阻塞队列,它作为静态内部类就在ScheduledThreadPoolExecutor中进行了实现
// DelayedWorkQueue是一个无界队列,它能按一定的顺序对工作队列中的元素进行排列
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}
  1. SingleThreadExecutor
// 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务
// 保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

上面四种方式均能使用 Executors 创建出来

Alibaba命名规范

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明: Executors 返回的线程池对象的弊端如下:

1) FixedThreadPool 和 SingleThreadPool : 允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM

2) CachedThreadPool 和 ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值