JAVA并发编程:详解线程池的工作原理及应用

1、为什么要使用线程池?

  在Java 并发编程中,线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的应用程序都可以使用线程池。在开发过程中,合理地使用线程池能够带来以下几个好处。

  • 降低资源消耗。通过重复利用已创建的线程降低线程的创建和销毁造成的资源消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行,从而提高应用系统的响应速度。
  • 提高线程的可管理性。线程属于稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以将线程进行统一分配、调优和 监控等。

2、线程池核心类的类关系

  线程池中核心的类和接口大致有以下几个,它们分别是:

  • Executor:它是一个接口,是 Executor 框架的基础,将任务的提交与任务的执行分离开来。
  • ExecutorService: 它是一个接口,继承了 Executor,在其上做了一些扩展,如 shutdown()、submit() 等,可以说是真正的线程池接口。
  • AbstractExecutorService:它是一个抽象类,实现了 ExecutorService 接口中的大部分方法。
  • ThreadPoolExecutor: 它线程池的核心实现类,用来执行被提交的任务。
  • ScheduledExecutorService:它是一个接口,继承了 ExecutorService 接口,提供了带"周期执行"功能的 ExecutorService。
  • ScheduledThreadPoolExecutor:它是一个实现类,实现 ScheduledExecutorService 接口,可以在给定的延迟后执行任务, 或者定期执行任务。它比 Timer 更灵活,功能更强大。

它们之间类的UML图如下所示:
在这里插入图片描述

3、线程池的生命周期状态

  线程池的生命周期状态,一共5种,线程池中使用一个 AtomicInteger 的 ctl 变量表示线程池的状态和数量,高3位表示线程池状态,低29位表示线程数量。

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;
  • RUNNING: 接受新任务并处理排队的任务,一旦线程被创建初始状态就是 RUNNING。
  • SHUTDOWN: 不接受新任务,但处理排队的任务和正在执行的任务。
  • STOP: 不接受新任务,不处理排队的任务,并中断正在进行的任务。
  • TIDYING: 所有任务都已终止,workerCount(线程数)为零,线程状态过渡到 TIDYING 状态将要执行终止方法 terminated()。
  • TERMINATED: 终止方法 terminated() 执行完成

各状态之间转换如下所示:

  • RUNNING -> SHUTDOWN :调用 shutdown() 方法,可能隐含在finalize()中。
  • (RUNNING or SHUTDOWN) -> STOP :调用 shutdownNow() 方法
  • SHUTDOWN -> TIDYING:当队列和线程池都为空时2111
  • STOP -> TIDYING: 当线程池为空时
  • TIDYING -> TERMINATED:当 terminated() 方法执行完成
    在这里插入图片描述

  使用一个变量 ctl 表示线程池的两个变量值,高3位表示线程池状态,低29位表示线程数量。
  为什么要这样设计,不用两个变量,使用一个变量来表示呢?一个变量可以使用一条CAS指令,可以无锁,保证原子性。如果是两个变量的话,就要使用两条CAS指令,不能保证原子性。

4、线程池的工作原理

  1)线程池中一开始是不存在线程的,当一个任务被提交给线程池后,线程池会创建一个新线程来执行任务。如果当前线程池中运行的线程数少于corePoolSize,则创建新线程来执行任务。
  2)如果线程池中运行的线程数达到核心线程数corePoolSize的上限,则将新提交的任务加入BlockingQueue队列当中去。
  3)如果无法将新提交的任务加入BlockingQueue队列中(有界队列,队列已满),则创建新的线程来执行任务,前提是maximumPoolSize大于corePoolSize。
  4)如果线程池中线程数达到maximumPoolSize,任务将被拒绝,执行拒绝策略,并调用RejectedExecutionHandler接口的rejectedExecution()方法。

特别注意:如果线程池的核心线程数被设置为0,那么提交的任务会启用空闲线程来执行。

线程池的工作原理图如下所示:
在这里插入图片描述

5、ThreadPoolExecutor 类的构造方法

  ThreadPoolExecutor 类一共有 4 个构造方法,如下图所示:
在这里插入图片描述
  接下来分析一下参数最多的一个构造方法,其他的几个构造方法不再过多讲述。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

此方法一共有7个参数:
  1)corePoolSize: 核心线程数
  2)maximumPoolSize: 最大线程数
  3)keepAliveTime: maximumPoolSize-corePoolSize 线程数(空闲线程)的存活时间,如果超时,则把线程销毁。
  4)unit:参数 keepAliveTime 的时间单位
  5)workQueue:任务存放的队列
  6)threadFactory:线程工程,主要是给线程取一个自定义的名字
  7)handler:拒绝策略

6、如何合理地配置线程池

  线程池的大小决定着系统的性能,数量过大或者过小线程池都没有办法发挥最优的系统性能。那如何合理地配置线程池?想必这是每个开发人员必须要了解的知识点,想要合理地配置线程池,就必须首先分析任务特性,可以从以下几个方面来分析。

  • 任务性质:CPU密集型、IO密集型和混合型。
  • 任务优先级:高、中、低。
  • 任务执行时间:长、中、短。
  • 任务依赖性:是否依赖其他系统资源,比如数据库连接等等。

定义下文中的几个变量:

  1. NCPU:表示处理器(CPU)的核心数目,Java可以通过Runtime.getRuntime().availableProcessors()方法获取。
  2. UCPU:表示期望CPU 的利用率(介于 0 和 1 之间)
  3. W/C:表示等待时间与计算时间的比率。等待时间与计算时间在 Linux 下使用相关的 vmstat 命令或者 top 命令查看。

CPU密集型任务:
  尽量使用较小的线程池数量,一般为 NCPU+1。 因为CPU密集型任务使得CPU使用率很高,若设置过大的线程数,会造成CPU过度切换,反而会降低系统的性能。

OI密集型任务:
  可以使用稍微大的线程池数量,一般为 NCPU*2 。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。在进行I/O操作的时候,是将任务交给DMA(Direct Memory Access)来处理,请求发出后CPU就不管了,在DMA处理完后通过中断通知CPU处理完成了,I/O操作消耗的CPU时间很少。
  对于IO密集型任务的最佳线程数,有个最佳计算公式:线程数 = NCPU * UCPU * (1 + W/C)。

混合型任务:
  可以将任务拆分成IO密集型任务和CPU密集型任务,使用不同的线程池去执行提交的任务。确保拆分完成后两个任务的执行时间没有明显的差距,那么这样的拆分可以给系统带来效率的提升;如果有明显的差距,则没有必要拆分。

  优先级不同的任务可以使用优先级队列 PriorityBlockingQueue 来处理。它可 以让优先级高的任务先执行。
  执行时间不同的任务可以交给不同规模的线程池来处理,或者可以使用优先 级队列,让执行时间短的任务先执行。
  依赖数据库连接池的任务,因为线程提交 SQL 后需要等待数据库返回结果, 等待的时间越长,则 CPU 空闲时间就越长,那么线程数应该设置得越大,这样才能更好地利用 CPU。

7、示例分析

7.1 深入解析线程池各参数的基本应用

  第一、核心线程和空闲线程(最大线程数 - 核心线程数)的基本应用
  定义核心线程数为1,最大线程数为2,队列长度为2,主线程提交4个任务。

package com.enjoy.lspj.threadpool;

import java.text.SimpleDateFormat;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 核心线程和空闲线程(最大线程数 - 核心线程数)的基本应用
 * <p>
 * 定义:核心线程数为1,最大线程数为2,队列长度为2,主线程提交4个任务。
 * 1、task1被线程t1执行
 * 2、task4被线程t2执行
 * 3、task2、task3放在阻塞队列中,等待线程t1或者t2执行完再去队列中取排队的任务。
 * 空闲线程和核心线程都会从队列当中随机去获取任务,前提是空闲线程被启用。
 */
public class UseThreadPoolExecutor1 {
    final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    private static AtomicInteger i = new AtomicInteger();
    private static ExecutorService executorService = new ThreadPoolExecutor(1,
            2,
            1,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2), (r) -> {
        // 给线程取个名字
        return new Thread(r, " t" + i.incrementAndGet());
    }, new ThreadPoolExecutor.AbortPolicy());


    public static void main(String[] args) {

        for (int j = 1; j <= 4; j++) {
            int finalJ = j;
            executorService.execute(() -> {
                System.out.println(getSystemTime() + " 线程名称:" + Thread.currentThread().getName() + " 正在执行 task" + finalJ);

                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getSystemTime() + " 线程名称:" + Thread.currentThread().getName() + " task" + finalJ + " 执行完毕。。。。。。 ");

            });
        }

    }

    private static String getSystemTime() {
        return sdf.format(System.currentTimeMillis());
    }
}

执行结果如下:
在这里插入图片描述
结果分析:
  task1被线程t1执行;task4被线程t2执行;task2、task3放在阻塞队列中,等待线程t1或者t2执行完再去队列中取排队的任务。空闲线程和核心线程都会从队列当中随机去获取任务,前提是空闲线程被启用。

  第二、空闲线程(最大线程数 - 核心线程数)超时的基本应用,存活的线程。
  定义核心线程数为1,最大线程数为2,队列长度为1,主线程提交3个任务。

package com.enjoy.lspj.threadpool;

import java.text.SimpleDateFormat;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 空闲线程(最大线程数 - 核心线程数)超时的基本应用,存活的线程。
 * <p>
 * 定义:核心线程数为1,最大线程数为2,队列长度为1,主线程提交3个任务。
 * 1、task1被线程t1执行
 * 2、task3被线程t2执行
 * 3、task2放在阻塞队列中,等待线程t1或者t2执行完再去队列中取排队的任务。
 * 空闲线程和核心线程都会从队列当中随机去获取任务,前提是空闲线程被启用。
 * 当核心线程抢到了队列中的任务task2执行;空闲线程一直处于空闲状态,timeout 结束,空闲线程被销毁。
 */
public class UseThreadPoolExecutor2 {
    final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    private static AtomicInteger i = new AtomicInteger();
    private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
            2,
            2,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(1), (r) -> {
        // 给线程取个名字
        return new Thread(r, " t" + i.incrementAndGet());
    }, new ThreadPoolExecutor.AbortPolicy());


    public static void main(String[] args) {

        for (int j = 1; j <= 3; j++) {
            int finalJ = j;
            threadPoolExecutor.execute(() -> {
                System.out.println(getSystemTime() + " 当前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " 正在执行 task" + finalJ);

                try {
                    // 当核心线程抢到了队列中的任务task2执行;空闲线程一直处于空闲状态,timeout 结束,空闲线程被销毁。
                    Thread.currentThread().sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getSystemTime() + " 业务执行结束,目前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " task" + finalJ + " 执行完毕。。。。。。 ");

            });
        }

    }

    private static String getSystemTime() {
        return sdf.format(System.currentTimeMillis());
    }
}

执行结果如下:
在这里插入图片描述
结果分析:
  task1被线程t1执行;task3被线程t2执行;task2放在阻塞队列中,等待线程t1或者t2执行完再去队列中取排队的任务。空闲线程和核心线程都会从队列当中随机去获取任务,前提是空闲线程被启用。当核心线程抢到了队列中的任务task2执行;空闲线程一直处于空闲状态,timeout 结束,空闲线程被销毁,此时线程池中的线程数量为1。

7.2 拒绝策略

  线程池的拒绝策略,当线程池的阻塞队列满了且没有空闲的工作线程时,如果继续往线程池中提交任务,那么就需要采取一种策略来处理提交的任务,线程池给我们提供了4种策略,方便调用者来选择合适的拒绝策略,它们分别是:
  1)AbortPolicy:直接抛出异常,默认策略;执行此策略,线程池不能被shutdown。
  2)CallerRunsPolicy:用调用者所在的线程来执行提交的任务;
  3)DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
  4)DiscardPolicy:直接丢弃任务;
  我们也可以根据实际开发中的应用场景实现自定义拒绝策略,实现接口 RejectedExecutionHandler 即可,比如记录日志或持久化存储不能处理的任务等等。

package com.enjoy.lspj.threadpool;

import java.text.SimpleDateFormat;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 执行拒绝策略:
 * 线程池的拒绝策略,当线程池的阻塞队列满了且没有空闲的工作线程时,如果继续往线程池中提交任务,那么就需要采取一种策略来处理提交的任务,
 * 线程池给我们提供了4种策略,方便调用者来选择合适的拒绝策略,它们分别是:
 * 1、AbortPolicy:直接抛出异常,默认策略;执行此策略,线程池不能被shutdown。
 * 2、CallerRunsPolicy:用调用者所在的线程来执行提交的任务;
 * 3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
 * 4、DiscardPolicy:直接丢弃提交的任务;
 * 我们也可以根据实际开发中的应用场景实现自定义拒绝策略,实现接口 RejectedExecutionHandler 即可,比如记录日志或持久化存储不能处理的任务等等。
 * <p>
 * 定义:核心线程数为1,最大线程数为2,队列长度为1,主线程提交4个任务。
 * 1、task1被线程t1执行
 * 2、task3被线程t2执行
 * 3、task2放在阻塞队列中,等待线程t1或者t2执行完再去队列中取排队的任务。
 * 空闲线程和核心线程都会从队列当中随机去获取任务,前提是空闲线程被启用。
 * 4、主线程提交任务task4,由于队列已满,线程数达到最大数且还在执行其他任务,因此线程池将执行拒绝策略。
 *
 */
public class UseThreadPoolExecutor3 {
    final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    private static AtomicInteger i = new AtomicInteger();

    private static String getSystemTime() {
        return sdf.format(System.currentTimeMillis());
    }

    /**
     * 拒绝策略:AbortPolicy
     * 如果执行此拒绝策略,直接抛出异常,且线程池不能被shutdown。
     */
    private static void m1(){

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
                2,
                2,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1), (r) -> {
            // 给线程取个名字
            return new Thread(r, " t" + i.incrementAndGet());
        }, new ThreadPoolExecutor.AbortPolicy());

        for (int j = 1; j <= 4; j++) {
            int finalJ = j;
            try {
                Thread.currentThread().sleep(500);
                System.out.println(getSystemTime() + " main 线程准备提交任务:task" + finalJ);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            threadPoolExecutor.execute(() -> {
                System.out.println(getSystemTime() + " 当前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " 正在执行 task" + finalJ);

                try {
                    Thread.currentThread().sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getSystemTime() + " 业务执行结束,目前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " task" + finalJ + " 执行完毕。。。。。。 ");

            });
        }
        threadPoolExecutor.shutdownNow();
        System.out.println(getSystemTime() + "线程名称:" + Thread.currentThread().getName() + " 关闭线程池......");
    }

    /**
     * 拒绝策略:CallerRunsPolicy
     * 如果执行此拒绝策略,用调用者所在的线程来执行提交的任务;
     */
    private static void m2(){

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
                2,
                2,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1), (r) -> {
            // 给线程取个名字
            return new Thread(r, " t" + i.incrementAndGet());
        }, new ThreadPoolExecutor.CallerRunsPolicy());

        for (int j = 1; j <= 4; j++) {
            int finalJ = j;
            try {
                Thread.currentThread().sleep(500);
                System.out.println(getSystemTime() + " main 线程准备提交任务:task" + finalJ);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            threadPoolExecutor.execute(() -> {
                System.out.println(getSystemTime() + " 当前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " 正在执行 task" + finalJ);

                try {
                    Thread.currentThread().sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getSystemTime() + " 业务执行结束,目前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " task" + finalJ + " 执行完毕。。。。。。 ");

            });
        }
        threadPoolExecutor.shutdown();
        System.out.println(getSystemTime() + "线程名称:" + Thread.currentThread().getName() + " 关闭线程池......");
    }

    /**
     * 拒绝策略:DiscardOldestPolicy
     * 如果执行此拒绝策略,丢弃阻塞队列中靠最前的任务,并执行当前任务;
     */
    private static void m3(){

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
                2,
                2,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1), (r) -> {
            // 给线程取个名字
            return new Thread(r, " t" + i.incrementAndGet());
        }, new ThreadPoolExecutor.DiscardOldestPolicy());

        for (int j = 1; j <= 4; j++) {
            int finalJ = j;
            try {
                Thread.currentThread().sleep(500);
                System.out.println(getSystemTime() + " main 线程准备提交任务:task" + finalJ);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            threadPoolExecutor.execute(() -> {
                System.out.println(getSystemTime() + " 当前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " 正在执行 task" + finalJ);

                try {
                    Thread.currentThread().sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getSystemTime() + " 业务执行结束,目前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " task" + finalJ + " 执行完毕。。。。。。 ");

            });
        }
        threadPoolExecutor.shutdown();
        System.out.println(getSystemTime() + "线程名称:" + Thread.currentThread().getName() + " 关闭线程池......");
    }

    /**
     * 拒绝策略:使用 DiscardPolicy
     * 如果执行此拒绝策略,直接丢弃提交的任务;
     */
    private static void m4(){

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
                2,
                2,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1), (r) -> {
            // 给线程取个名字
            return new Thread(r, " t" + i.incrementAndGet());
        }, new ThreadPoolExecutor.DiscardPolicy());

        for (int j = 1; j <= 4; j++) {
            int finalJ = j;
            try {
                Thread.currentThread().sleep(500);
                System.out.println(getSystemTime() + " main 线程准备提交任务:task" + finalJ);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            threadPoolExecutor.execute(() -> {
                System.out.println(getSystemTime() + " 当前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " 正在执行 task" + finalJ);

                try {
                    Thread.currentThread().sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getSystemTime() + " 业务执行结束,目前线程池中线程数:" + threadPoolExecutor.getPoolSize() + " 线程名称:" + Thread.currentThread().getName() + " task" + finalJ + " 执行完毕。。。。。。 ");

            });
        }
        threadPoolExecutor.shutdown();
        System.out.println(getSystemTime() + "线程名称:" + Thread.currentThread().getName() + " 关闭线程池......");
    }

    public static void main(String[] args) {
        m1();
        //m2();
        //m3();
        //m4();
    }


}

拒绝策略:AbortPolicy,方法m1()执行结果如下:
在这里插入图片描述
结果分析:
  从执行结果来看,如果使用 AbortPolicy 作为线程池的拒绝策略,当队列已满,线程数达到最大数且还在执行其他任务,如果主线程再往线程池中提交任务(task4),线程池将执行拒绝策略,直接抛出异常 RejectedExecutionException。后续线程池调用shutdown也不会被执行,因此线程池也不会被关闭。

拒绝策略:CallerRunsPolicy,方法m2()执行结果如下:
在这里插入图片描述
结果分析:
  从执行结果来看,如果使用 CallerRunsPolicy 作为线程池的拒绝策略,当队列已满,线程数达到最大数且还在执行其他任务,如果主线程再往线程池中提交任务(task4),线程池将执行拒绝策略,任务(task4)将被调用者所在的线程来执行,后续线程池调用shutdown会被执行关闭。

拒绝策略:DiscardOldestPolicy,方法m3()执行结果如下:
在这里插入图片描述
结果分析:
  从执行结果来看,如果使用 DiscardOldestPolicy 作为线程池的拒绝策略,当队列已满,线程数达到最大数且还在执行其他任务,如果主线程再往线程池中提交任务(task4),线程池将执行拒绝策略,丢弃阻塞队列中靠最前的任务(task2),并执行当前任务(task4),后续线程池调用shutdown会被执行关闭。

拒绝策略:DiscardPolicy,方法m4()执行结果如下:
在这里插入图片描述
结果分析:
  从执行结果来看,如果使用 DiscardPolicy 作为线程池的拒绝策略,当队列已满,线程数达到最大数且还在执行其他任务,如果主线程再往线程池中提交任务(task4),线程池将执行拒绝策略,直接丢弃提交的任务(task4),后续线程池调用shutdown会被执行关闭。

7.3 线程池的扩展

  自定义一个扩展类继承ThreadPoolExecutor类,重写父类的beforeExecute方法和afterExecute方法,可以实现在每个任务执行前后做一些操作,相当于执行了一个切面。 重写terminated方法,可以实现在调用shutdown后执行terminated方法。

package com.enjoy.lspj.threadpool;

import java.text.SimpleDateFormat;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 线程池的扩展
 * <p>
 * 自定义一个扩展类继承ThreadPoolExecutor类,重写父类的beforeExecute方法和afterExecute方法,
 * 可以实现在每个任务执行前后做一些操作,相当于执行了一个切面。
 * 重写terminated方法,可以实现在调用shutdown后执行terminated方法。
 */
public class UseThreadPoolExecutorExt4 extends ThreadPoolExecutor {
    final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    private static UseThreadPoolExecutorExt4 useThreadPoolExecutor4 = new UseThreadPoolExecutorExt4(1, 1, 0, TimeUnit.NANOSECONDS, new ArrayBlockingQueue<>(2));


    public UseThreadPoolExecutorExt4(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行前置业务");
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行后置业务");
    }

    @Override
    protected void terminated() {
        System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " shutdown 后执行");
    }

    public static void main(String[] args) {
        useThreadPoolExecutor4.execute(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行业务操作。。。");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        useThreadPoolExecutor4.shutdown(); // 调用shutdown后执行terminated方法
    }

    private static String getSystemTime() {
        return sdf.format(System.currentTimeMillis());
    }
}

执行结果如下:
在这里插入图片描述
结果分析系:
  从执行结果来看,可以看到,每个任务执行前后都会调用 beforeExecute 和afterExecute 方法,相当于执行了一个切面。而在调用 shutdown 方法后则会调用 terminated 方法。

7.4 提交任务

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

package com.enjoy.lspj.threadpool;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 线程池的提交任务
 */
public class UseThreadPoolExecutor5 {
    private final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    private static AtomicInteger i = new AtomicInteger();
    private static ExecutorService executorService = new ThreadPoolExecutor(1,
            2,
            1,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2), (r) -> {
        // 给线程取个名字
        return new Thread(r, " t" + i.incrementAndGet());
    }, new ThreadPoolExecutor.AbortPolicy());

    private static String getSystemTime() {
        return sdf.format(System.currentTimeMillis());
    }


    /**
     * 提交一个任务,任务不需要有返回值
     */
    private static void m1() {
        executorService.execute(() -> {
            System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + "执行任务");
        });

    }

    /**
     * 提交需要有返回值的任务
     * 调用 future 的 get 方法获取返回值
     * 1、调用无参数的get方法,主线程会等待任务执行完返回
     * 2、调用有参数的get方法,超时获取任务返回值,如在设定的时间范围内未获取数据,则将超时抛异常
     */
    private static void m2() throws ExecutionException, InterruptedException {

        Future<String> future = executorService.submit(() -> {
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 正在执行任务");
            TimeUnit.SECONDS.sleep(2);
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行完任务,准备返回!!!");
            return "SUCCESS";
        });

        // 调用get方法会阻塞,直到获取任务的返回值
        System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " 获取返回值,result=" + future.get());
        System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " end ...");
    }

    /**
     * 提交需要有返回值的任务
     * 调用 future 的 get 方法获取返回值
     * 1、调用无参数的get方法,主线程会等待任务执行完返回
     * 2、调用有参数的get方法,超时获取任务返回值,如在设定的时间范围内未获取数据,则将超时抛异常
     */
    private static void m3() throws ExecutionException, InterruptedException, TimeoutException {

        Future<String> future = executorService.submit(() -> {
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 正在执行任务");
            TimeUnit.SECONDS.sleep(2);
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行完任务,准备返回!!!");
            return "SUCCESS";
        });

        // 调用get方法超时获取返回值,如在设定的时间范围内未获取数据,则将超时抛异常
        System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " 超时获取返回值,result=" + future.get(1, TimeUnit.SECONDS));
        System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " end ...");
    }


    /**
     * 批量执行任务
     * 执行给定的任务,当所有任务都执行完成后,返回其执行结果和状态列表
     *
     * @throws InterruptedException
     */
    private static void m4() throws InterruptedException {
        ExecutorService executorServiceAll = new ThreadPoolExecutor(3,
                3,
                1,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(2), (r) -> {
            // 给线程取个名字
            return new Thread(r, " t" + i.incrementAndGet());
        }, new ThreadPoolExecutor.AbortPolicy());

        List<Future<String>> futures = executorServiceAll.invokeAll(Arrays.asList(
                () -> {
                    System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " 执行任务 1");
                    return "执行结果1";
                }, () -> {
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " 执行任务 2");
                    return "执行结果2";
                }, () -> {
                    System.out.println(getSystemTime() + " 线程 " + Thread.currentThread().getName() + " 执行任务 3");
                    return "执行结果3";
                }
        ));
        System.out.println(getSystemTime() + " main start...");
        futures.forEach(f -> {
            try {
                System.out.println(getSystemTime() + " 获取返回结果,result=" + f.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
        System.out.println(getSystemTime() + " main end...");


    }


    /**
     * 执行批量任务:返回最先执行完成任务的结果
     * 执行给定的任务,返回一个已成功完成的任务的结果(例如,没有抛出异常),
     * 如果有的话,在正常或异常返回时,未完成的任务将被取消。
     * 如果在执行此操作时修改了给定的集合,则此方法的结果不明确。
     *
     * @throws ExecutionException
     * @throws InterruptedException
     */
    private static void m5() throws ExecutionException, InterruptedException {

        ExecutorService executorServiceAny = new ThreadPoolExecutor(4,
                4,
                1,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(2), (r) -> {
            // 给线程取个名字
            return new Thread(r, " t" + i.incrementAndGet());
        }, new ThreadPoolExecutor.AbortPolicy());

        String value = executorServiceAny.invokeAny(Arrays.asList(
                () -> {

                    System.out.println(getSystemTime() + " 执行任务1 begin ...");
                    TimeUnit.MILLISECONDS.sleep(3000);
                    System.out.println(getSystemTime() + " 执行任务1 end ...");
                    return "执行结果1";
                }, () -> {
                    System.out.println(getSystemTime() + " 执行任务2 begin ...");
                    TimeUnit.MILLISECONDS.sleep(500);
                    System.out.println(getSystemTime() + " 执行任务2 end ...");

                    return "执行结果2";
                }, () -> {
                    System.out.println(getSystemTime() + " 执行任务3 begin ...");
                    TimeUnit.MILLISECONDS.sleep(2000);
                    System.out.println(getSystemTime() + " 执行任务3 end ...");
                    return "执行结果3";
                }, () -> {
                    System.out.println(getSystemTime() + " 执行任务4 begin ...");
                    TimeUnit.MILLISECONDS.sleep(2000);
                    System.out.println(getSystemTime() + " 执行任务4 end ...");
                    return "执行结果4";
                }
        ));
        System.out.println(getSystemTime() + " main start ......");
        System.out.println(getSystemTime() + " 执行结果,result=" + value);
        System.out.println(getSystemTime() + " main end ......");

    }

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        //m1();
        //m2();
        //m3();
        //m4();
        m5();
    }
}

方法m1()执行结果:
在这里插入图片描述
  提交一个任务,任务不需要有返回值,简单得不能再简单了,调用线程池的 execute() 方法即可。

方法m2()执行结果:
在这里插入图片描述
  提交一个任务,任务需要有返回值,调用 future 对象的无参数的 get() 方法,主线程会等待任务执行完返回后再执行其他操作。

方法m3()执行结果:
在这里插入图片描述
  提交一个任务,任务需要有返回值,调用 future 对象有参数的 get() 方法,超时获取任务返回值,如在设定的时间范围内未获取数据,则将超时抛异常 TimeoutException。

方法m4()执行结果:
在这里插入图片描述
  批量执行任务,执行给定的任务,当所有任务都执行完成后,返回其执行结果和状态列表。

方法m5()执行结果:
在这里插入图片描述
  执行批量任务,返回最先执行完成任务的结果,未执行完成的任务将会放弃执行。

7.5 关闭线程池

  关闭线程池,可以通过调用线程池的 shutdown() 或 shutdownNow()方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt() 方法来中断线程,所以无法响应中断的任务可能永远无法终止。两个关闭线程池的方法存在一定的区别,shutdownNow() 首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而 shutdown() 只是将线程池的状态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。
  线程池只要调用了上面介绍的这两个关闭方法中的任意一个,再调用 isShutdown() 方法会返回true。当所有的任务都已经执行完后,才表示线程池关闭成功,这时调用 isTerminated() 方法会返回true。至于应该使用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用 shutdown() 方法来关闭线程池,如果任务可以不用执行完,则可以调用 shutdownNow() 方法来关闭线程池。
  线程池调用 shutdown() 方法后,调用线程并不会等待所有任务都执行结束,而是继续执行其他业务操作。如果想要等待线程池的所有任务都执行结束,调用线程再去执行其他操作的话,可以调用此方法来超时等待线程池执行任务 boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;

package com.enjoy.lspj.threadpool;

import java.text.SimpleDateFormat;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 关闭线程池,一共有两种方式
 * 1、调用 shutdown() 方法
 * 2、调用 shutdownNow() 方法
 */
public class UseThreadPoolExecutor6 {
    private final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    private static AtomicInteger i = new AtomicInteger();
    private static ExecutorService executorService = new ThreadPoolExecutor(
            2,
            2,
            1,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(10),
            (r) -> {
                // 给线程取个名字
                return new Thread(r, " t" + i.incrementAndGet());
            },
            new ThreadPoolExecutor.AbortPolicy());

    private static String getSystemTime() {
        return sdf.format(System.currentTimeMillis());
    }


    /**
     * 调用 shutdown() 方法关闭线程池
     * 最多2个线程,提交4个任务,其中1个任务会在队列当中,1个任务不会被正常提交
     * 备注:调用了shutdown() 方法,线程池状态变为 SHUTDOWN,不在接受新的任务,但是线程池还是会执行正在执行的任务和队列中的任务
     *
     * @throws InterruptedException
     */
    private static void shutdown1() throws InterruptedException {


        // t1 执行 task1
        executorService.execute(() -> {
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task1--start----");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task1--end----");
        });


        // t2 执行 task2
        executorService.execute(() -> {
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task2--start----");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task2--END----");
        });


        // t1 || t2  正在执行任务 , task3 放入队列
        executorService.execute(() -> {
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task3--start----");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task3--END----");
        });

        // 执行shutdown
        System.out.println(getSystemTime() + " shutdown");
        executorService.shutdown();


        // 由于调用了线程池的 shutdown() 方法,线程池不在接受新任务
        executorService.execute(() -> {
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task4--start----");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task4--END----");
        });

    }


    /**
     * 调用 shutdown后,调用线程并不会等待所有任务运行结束,可以调用此方法来超时等待
     * boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
     * 在线程池状态终结之后做一些其他工作
     * @throws InterruptedException
     */
    private static void shutdown2() throws InterruptedException {
        //t1
        executorService.execute(()->{
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task1--start----");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task1--END----");
        });

        //t2
        executorService.execute(()->{
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task2--start----");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task2--END----");
        });


        // t1 || t2
        executorService.execute(()->{
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task3--start----");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task3--END----");
        });

        System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 shutdown");
        executorService.shutdown();
        //等待线程池成为终结状态执行,超时等待,为了线程池终结之后做一些其他工作
        executorService.awaitTermination(13,TimeUnit.SECONDS);
        System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 main  end");
    }

    /**
     * 调用 shutdown() 方法关闭线程池
     * 最多2个线程,提交4个任务,其中1个任务会在队列当中,1个任务不会被正常提交
     * 备注:调用了shutdownNow() 方法,线程池状态变为 STOP,不在接受新的任务,不处理排队的任务,会将队列中的任务返回,并用 interrupt 的方式中断正在执行的任务
     *
     * @throws InterruptedException
     */
    private static void shutdownNow() {
        //t1 执行 task1
        executorService.execute(()->{
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task1--start----");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task1--END----");
        });

        //t2 执行 task1
        executorService.execute(()->{
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task2--start----");
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task2--END----");
        });

        // t1 || t2  正在执行任务 , task3 放入队列
        executorService.execute(()->{
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task3--start----");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task3--END----");
        });

        System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 shutdownNow");
        //返回的是队列当中排队的任务
        List<Runnable> runnables = executorService.shutdownNow();
        System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 queue size: " + runnables.size());

        // task4 线程池调用 shutdownNow() 方法后,线程池状态为 STOP ,不再接受新任务
        executorService.execute(()->{
            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task4--start----");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(getSystemTime() + " 线程" + Thread.currentThread().getName() + " 执行 task4--END----");
        });

    }



    
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {

        shutdown1();
        //shutdown2();
        //shutdownNow();
    }
}

shutdown1()方法,执行结果如下:
在这里插入图片描述
  从执行结果来看,当调用线程池的 shutdown() 方法后,线程池不再接收新的任务(task4),但是线程池还是会执行正在执行的任务和队列中的任务。

shutdown2()方法,执行结果如下:
在这里插入图片描述
  从执行结果来看,当调用 shutdown() 方法后,调用线程并不会等待所有任务运行结束,而是继续执行自己的业务操作,可以调用此方法来超时等待 awaitTermination(long timeout, TimeUnit unit) 在线程池状态终结之后做一些其他工作。

shutdownNow()方法,执行结果如下:
在这里插入图片描述
  从执行结果来看,当调用 shutdownNow() 方法后,线程池不在接收新的任务,不处理排队的任务,会将队列中的任务返回,并用 interrupt 的方式中断正在执行的任务。

  线程池的工作原理及其基本应用就分析到这里啦,作者能力有限,有分析不到位的地方欢迎各位读者留言讨论,下一篇将详细讲解使用 Executors 工厂创建线程池的5种方法。


备注:博主微信公众号,不定期更新文章,欢迎扫码关注。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值