【JavaSE】线程池的使用

目录

一 、概述

二 、线程池的执行流程

三 、线程池的配置参数

四 、线程池分类 

五 、线程池的状态

六 、自定义线程池

七 、线程池的四种拒绝策略


一 、概述

线程池内部维护了若干个线程,没有任务的时候,这些线程都处于等待空闲状态。如果有新的线程任务,就分配一个空闲线程执行。如果所有线程都处于忙碌状态,线程池会创建一个新线程进行处理或者放入队列(工作队列)中等待。

二 、线程池的执行流程

  • 提交一个新线程任务,线程池会在线程池中分配一个空闲线程,用于执行线程任务;
  • 如果线程池中不存在空闲线程,则线程池会判断当前“存活的线程数”是否小于核心线程数corePoolSize。
  • 如果小于核心线程数corePoolSize,线程池会创建一个新线程(核心线程)去处理新线程任务;
  • 如果大于核心线程数corePoolSize,线程池会检查工作队列;
  • 如果工作队列未满,则将该线程任务放入工作队列进行等待。线程池中如果出现空闲线程,将从工作队列中按照FIFO的规则取出1个线程任务并分配执行;
  • 如果工作队列已满,则判断线程数是否达到最大线程数maximumPoolSize;
  • 如果当前“存活线程数”没有达到最大线程数maximumPoolSize,则创建一个新线程(非核心线程)执行新线程任务;
  • 如果当前“存活线程数”已经达到最大线程数maximumPoolSize,直接采用拒绝策略处理新线程任务;

综上所述,执行顺序为:核心线程、工作队列、非核心线程、拒绝策略。

【JavaSE】线程池的使用_第1张图片

三 、线程池的配置参数

  • corePoolSize线程池核心线程数:也可以理解为线程池维护的最小线程数量,核心线程创建后不会被回收。大于核心线程数的线程,在空闲时间超过keepAliveTime后会被回收
  • maximumPoolSize线程池最大线程数:线程池允许创建的最大线程数量(包含核心线程池数量)
  • keepAliveTime非核心线程线程存活时间:当一个可被回收的线程的空闲时间大于keepAliveTime,就会被回收
  • TimeUnit时间单位:参数keepAliveTime的时间单位
  • BlockingQueue阻塞工作队列:用来存储等待执行的任务
  • ThreadFactory线程工厂 : 用于创建线程,以及自定义线程名称,需要实现ThreadFactory接口
  • RejectedExecutionHandler拒绝策略:当线程池线程内的线程耗尽,并且工作队列达到已满时,新提交的任务,将使用拒绝策略进行处理

四 、线程池分类 

以下是Java库提供的几个常见的线程池可以通过Executors工具类直接创建 

  • FixedThreadPool:线程数固定的线程池,使用Executors.newFixedThreadPool()创建
    线程池参数:
    核心线程数和最大线程数一致
    非核心线程线程空闲存活时间,即keepAliveTime为0
    阻塞队列为无界队列LinkedBlockingQueue
    工作机制:
    a.提交线程任务
    b.如果线程数少于核心线程,创建核心线程执行任务
    c.如果线程数等于核心线程,把任务添加到LinkedBlockingQueue阻塞队列
    d.如果线程执行完任务,去阻塞队列取任务,继续执行
    使用场景:适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务
  • CachedThreadPool:线程数根据任务动态调整的线程池,使用Executors.newCachedThreadPool()创建
    线程池参数:
    核心线程数为0
    最大线程数为Integer.MAX_VALUE
    工作队列是SynchronousQueue同步队列
    非核心线程空闲存活时间为60秒
    工作机制:
    a.提交线程任务
    b.因为核心线程数为0,所以任务直接加到SynchronousQueue工作队列
    c.判断是否有空闲线程,如果有,就去取出任务执行
    d.如果没有空闲线程,就新建一个线程执行
    e.执行完任务的线程,还可以存活60秒,如果在这期间,接到任务,可以继续存活下去;否则,被销毁。
    使用场景: 用于并发执行大量短期的小任务。
  • SingleThreadExecutor:仅提供一个单线程的线程池,使用Executors.newSingleThreadExecutor()创建

    线程池参数:
    核心线程数为1
    最大线程数也为1
    阻塞队列是LinkedBlockingQueue
    非核心线程空闲存活时间为0秒
    使用场景:适用于串行执行任务的场景,将任务按顺序执行
  • ScheduledThreadPool:能实现定时、周期性任务的线程池,使用Executors.newScheduledThreadPool()创建

    线程池参数:
    最大线程数为Integer.MAX_VALUE
    阻塞队列是DelayedWorkQueue
    keepAliveTime为0
    使用场景: 周期性执行任务,并且需要限制线程数量的需求场景。
     

五 、线程池的状态

线程池的状态分为:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED

RUNNING:运行状态,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。该状态的线程池会接收新任务,并处理工作队列中的任务。

  • 调用线程池的shutdown()方法,可以切换到SHUTDOWN关闭状态;
  • 调用线程池的shutdownNow()方法,可以切换到STOP停止状态;

SHUTDOWN:关闭状态,该状态的线程池不会接收新任务,但会处理工作队列中的任务;

  • 当工作队列为空时,并且线程池中执行的任务也为空时,线程池进入TIDYING状态;

STOP:停止状态,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行 的任务;

  • 线程池中执行的任务为空,进入TIDYING状态;

TIDYING:整理状态,该状态表明所有的任务已经运行终止,记录的任务数量为0;

  • terminated()执行完毕,进入TERMINATED状态;

TERMINATED: 终止状态,该状态表示线程池彻底关闭。

【JavaSE】线程池的使用_第2张图片

六 、自定义线程池

上面虽然列举几个常见线程池,但是还是推荐一般使用自定义创建线程池

只需要根据自己需求来填写参数

        ExecutorService pool =new ThreadPoolExecutor(3,5,6,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),
                new MyThreadFactory()),
                new ThreadPoolExecutor.AbortPolicy());

当然,我们也可以在创建线程池时,使用自定义线程工厂规范线程命名方式,避免线程使用默认名称。

class MyThreadFactory implements ThreadFactory{
    // 具备原子性的Integer类型
    private final AtomicInteger number = new AtomicInteger(0);

    // 实现接口定义的抽象方法
    // 该方法作用:根据线程任务,创建线程对象(设置线程名称)
    @Override
    public Thread newThread(Runnable r) {
        // 将线程任务封装成Thread线程对象
        Thread t =new Thread(r);
        //设置线程名称
        t.setName("订单线程"+number.incrementAndGet());
        return t;
    }
}

七 、线程池的四种拒绝策略

  • 第一种拒绝策略是 AbortPolicy,这种拒绝策略在拒绝任务时,会直接抛出一个类型为 RejectedExecutionException 的 RuntimeException,让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略。
  • 第二种拒绝策略是 DiscardPolicy,这种拒绝策略正如它的名字所描述的一样,当新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。
  • 第三种拒绝策略是 DiscardOldestPolicy,如果线程池没被关闭且没有能力执行,则会丢弃任务队列中的头结点,通常是存活时间最长的任务,这种策略与第二种不同之处在于它丢弃的不是最新提交的,而是队列中存活时间最长的,这样就可以腾出空间给新提交的任务,但同理它也存在一定的数据丢失风险。
  • 第四种拒绝策略是 CallerRunsPolicy,相对而言它就比较完善了,当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。这样做主要有两点好处。
  1. 新提交的任务不会被丢弃,这样也就不会造成业务损失。
  2. 由于谁提交任务谁就要负责执行任务,这样提交任务的线程就得负责执行任务,而执行任务又是比较耗时的,在这段期间,提交任务的线程被占用,也就不会再提交新的任务,减缓了任务提交的速度,相当于是一个负反馈。在此期间,线程池中的线程也可以充分利用这段时间来执行掉一部分任务,腾出一定的空间,相当于是给了线程池一定的缓冲期。
     

你可能感兴趣的:(java,jvm,开发语言)