八、线程池

一、什么是线程池

1、池化产生的背景

1、在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其他更多资源。
2、在Java中,虚拟机将视图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗费资源的对象的创建和销毁。如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些“池化资源”技术产生的原因。

2、什么是线程池

1、线程池就是事先将多个线程对象放到一个容器中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。
2、线程池做的工作主要是控制运行的线程的数量,处理过程中将任务加入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出的数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
3、优点
  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

3、线程池核心类

1、Java中的线程池是通过Executor框架实现的,该框架中用到了Executor、Executors、ExecutorService、ThreadPoolExecutor这几个类
2、说明:
  • Executor:线程池的顶级接口,但严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。
  • ExecutorService:真正的线程池接口,继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny、shutDown等。
  • AbstractExecutorService:实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法。
  • ScheduledExecutorService:与Timer、TimerTask类似,解决需要任务重复执行的问题。
  • ThreadPoolExecutor:ExecutorService的默认实现,继承了类AbstractExecutorService。
  • ScheduledThreadPoolExecutor:继承ThreadPoolExecutor类,实现了ScheduledExecutorService接口,实现周期性任务的调度。
  • Executor:是线程的工厂类,方便快速创建线程池。

在这里插入图片描述

4、常用线程池

Executors类中提供了众多工厂方法来创建各种用途的线程池:
1、newSingleThreadPool:创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
2、newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
3、newCachedThreadPool:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
4、newScheduledThreadPool:创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。
5、newWorkStealingPool:JDK1.8 提供的线程池,底层使用的是ForkJoinPool实现,创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用CPU核数的线程来并行执 行任务。

二、使用Executors工厂类创建线程池

1、newFixedThreadPool线程池

1、创建一个可重用固定线程数的线程池,以共享的无界阻塞队列方式来运行这些线程。在任意点,在大多数线程会处于处理任务的活动状态。如果在所有任务处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务。在某个线程被显式地关闭之前,池中的线程将一直存在。
2、特点:
  • 线程池中的线程处于一定的量,可以很好的控制线程的并发量。
  • 线程可以重复使用,在显式关闭前就一直存在。
  • 超出一定量的线程被提交时需要在队列中等待。
3、实现代码:
  • 核心线程数等于最大线程数(没有救急线程被创建),因此也无需超时时间。
  • 阻塞队列是无界的,可以放任意数量的任务。

在这里插入图片描述

4、适用场景:适用于可以预测线程数量的业务中,或者服务器负载较重,对线程数有严格限制的场景。
public class ThreadPoolTest1 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 6; i++) {
            final int no = i;
            pool.execute(() -> {
                System.out.println("Thread " + no + " Start");
                try {
                    Thread.sleep(no * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread " + no + " End");
            });
        }
        pool.shutdown();
    }
}
/**
 * 运行结果如下:
 * 从结果可以看出,刚开始的时候2个线程开始执行,然后就结束一个执行一个,线程池里面永远2个线程在执行
 * Thread 1 Start
 * Thread 0 Start
 * Thread 0 End
 * Thread 2 Start
 * Thread 1 End
 * Thread 3 Start
 * Thread 2 End
 * Thread 4 Start
 * Thread 3 End
 * Thread 5 Start
 * Thread 4 End
 * Thread 5 End
 */

2、newSingleThreadPool线程池

1、创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
2、特点:线程池中最多执行1个线程,之后提交的线程将会排在队列中依次执行。
3、实现代码:核心线程数和最大线程数都为1

在这里插入图片描述

4、适用场景:希望多个任务排队执行。线程数固定为1,任务数多于1时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。
public class ThreadPoolTest2 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            final int no = i;
            pool.execute(() -> {
                System.out.println("Thread " + no + " Start");
                try {
                    Thread.sleep(no * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread " + no + " End");
            });
        }
        pool.shutdown();
    }
}
/**
 * 运行结果如下:
 * 从结果可以看出,每结束一个任务就执行一个任务,依次执行
 * Thread 0 Start
 * Thread 0 End
 * Thread 1 Start
 * Thread 1 End
 * Thread 2 Start
 * Thread 2 End
 * Thread 3 Start
 * Thread 3 End
 * Thread 4 Start
 * Thread 4 End
 */

3、newCachedThreadPool线程池

1、创建一个缓存池大小可根据需要伸缩的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有60s未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。
2、特点:
  • 线程池中数量没有固定,可达到最大值(Interger.MAX_VALUE)。
  • 线程池中的线程可进行缓存重复利用和回收(回收默认时间为60s)。
  • 当线程池中没有可用线程,会重新创建一个线程。
3、实现代码:
  • 核心线程数是0 ,最大线程数Integer.MAX_VALUE,救急线程的空闲生存时间是60s,意味着全部都是救急线程(60s后可以回收),救急线程可以无限创建。
  • 队列采用了SynchronousQueue,实现特点是,它没有容量,没有线程来取是放不进去队列中的。

在这里插入图片描述

4、适用场景:适合任务数比较密集,但每个任务执行时间较短的情况。
//整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲1分钟后释放线程。
public class ThreadPoolTest3 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            final int no = i;
            pool.execute(() -> {
                System.out.println("Thread " + no + " Start");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread " + no + " End");
            });
        }
        pool.shutdown();
    }
}
/**
 * 运行结果如下:
 * 从结果看,一下所有线程都开始执行,都在互相争抢CPU资源
 * Thread 1 Start
 * Thread 0 Start
 * Thread 4 Start
 * Thread 3 Start
 * Thread 2 Start
 * Thread 3 End
 * Thread 2 End
 * Thread 4 End
 * Thread 0 End
 * Thread 1 End
 */

4、newScheduledThreadPool线程池

1、创建一个大小无限并支持在给定延迟之后或周期性执行任务的线程池。在任务调度线程池功能加入之前,可以使用java.util.Timer来实现定时功能,Timer的优点在于简单易用,但是由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。
@Slf4j
public class ThreadPoolTest4 {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        //添加5个任务,延迟1s之后执行,只执行一次
        for (int i = 0; i < 5; i++) {
            final int no = i;
            log.info("thread " + no + " start");
            pool.schedule(() -> {
                log.info("thread " + no + " execute");
            }, 1000, TimeUnit.MILLISECONDS);
        }
    }
}
/**
 * 运行结果如下:
 * 10:58:12.474 [main] INFO com.itan.pool.ThreadPoolTest4 - thread 0 start
 * 10:58:12.670 [main] INFO com.itan.pool.ThreadPoolTest4 - thread 1 start
 * 10:58:12.670 [main] INFO com.itan.pool.ThreadPoolTest4 - thread 2 start
 * 10:58:12.670 [main] INFO com.itan.pool.ThreadPoolTest4 - thread 3 start
 * 10:58:12.670 [main] INFO com.itan.pool.ThreadPoolTest4 - thread 4 start
 * 10:58:13.675 [pool-1-thread-1] INFO com.itan.pool.ThreadPoolTest4 - thread 0 execute
 * 10:58:13.676 [pool-1-thread-1] INFO com.itan.pool.ThreadPoolTest4 - thread 1 execute
 * 10:58:13.676 [pool-1-thread-1] INFO com.itan.pool.ThreadPoolTest4 - thread 2 execute
 * 10:58:13.676 [pool-1-thread-1] INFO com.itan.pool.ThreadPoolTest4 - thread 3 execute
 * 10:58:13.676 [pool-1-thread-1] INFO com.itan.pool.ThreadPoolTest4 - thread 4 execute
 */
public class ThreadPoolTest5 {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        System.out.println(sdf.format(new Date()) + " " +  Thread.currentThread().getName() + " - start");
        //创建并执行一个周期性动作,该动作在给定的初始延迟后首先启用,随后在给定的周期内启用
        //参数说明:任务对象,初始延迟,延迟间隔,时间单位
        pool.scheduleAtFixedRate(()->{
            try {
                //如果此任务的任何执行时间超过其周期,则后续执行可能会延迟开始
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(sdf.format(new Date()) + " " + Thread.currentThread().getName() + " - running...");
        },1000,1000, TimeUnit.MILLISECONDS);
    }
}
/**
 * 运行结果如下:
 * 11:29:35 main - start
 * 11:29:39 pool-1-thread-1 - running...
 * 11:29:42 pool-1-thread-1 - running...
 * 11:29:45 pool-1-thread-1 - running...
 * 11:29:48 pool-1-thread-1 - running...
 */
public class ThreadPoolTest5 {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        System.out.println(sdf.format(new Date()) + " " +  Thread.currentThread().getName() + " - start");
        //创建并执行一个周期性操作,该操作首先在给定的初始延迟之后启用,随后在一个执行的终止和下一个执行的开始之间具有给定的延迟
        //参数说明:任务对象,初始延迟,延迟间隔,时间单位
        pool.scheduleWithFixedDelay(()->{
            System.out.println(sdf.format(new Date()) + " " + Thread.currentThread().getName() + " - running...");
            try {
                //无论此任务的执行时间是否超过其周期,后续任务执行都会延迟开始
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },1000,1000, TimeUnit.MILLISECONDS);
    }
}
/**
 * 运行结果如下:
 * 11:34:57 main - start
 * 11:34:58 pool-1-thread-1 - running...
 * 11:35:00 pool-1-thread-1 - running...
 * 11:35:02 pool-1-thread-1 - running...
 */
/**
 * 每天11时51分开始执行任务
 */
public class ThreadPoolTest5 {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        System.out.println(sdf.format(new Date()) + " " +  Thread.currentThread().getName() + " - start");
        //获取当前时间
        LocalDateTime now = LocalDateTime.now();
        //获取11:51:00
        LocalDateTime time = now.withHour(11).withMinute(51).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
        //如果当前时间 > 开始执行的时间,就往后一天
        if (now.compareTo(time) > 0){
            time = time.plusDays(1);
        }
        System.out.println(time);
        //计算当前时间和要执行任务的时间差
        long initailDelay = Duration.between(now, time).toMillis();
        //计算一天的间隔时间
        long period = 1000 * 60 * 60 * 24 * 1;
        pool.scheduleAtFixedRate(()->{
            System.out.println("running");
        },initailDelay, period, TimeUnit.MILLISECONDS);
    }
}
/**
 * 运行结果如下:
 * 11:52:18 main - start
 * 2022-07-01T11:51
 */

三、ThreadPoolExecutor使用

1、概述

JDK线程池的关键实现,常用的Executors中的预定义线程池就有这个类的实例,当然也可以通过该类的构造器来创建一个自定义的线程池,提供任务执行,线程调度,线程池管理等等服务。

2、ThreadPoolExecutor的几个主要属性

/**
 * 用来同时记录线程池的运行状态(runState,简称rs)和线程池中线程数量(workerCount,简称wc)。ctl的值使用AtomicInteger原子类包装,能够保证数据是线程安全的。
 * int类型转换为二进制之后的最高三位保存线程池的状态,低29位保存线程数量。刚初始化ctl的时候,rs为RUNNING状态,wc为0
 */
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

//线程数量掩码位数,int类型长度-3后的剩余位数,即wc所占位数为29
private static final int COUNT_BITS = Integer.SIZE - 3;

//为了能正确保存线程数量,线程池的数量线程被限制为29位的最大值,即最大(2^29)-1个,而不是(2^31)-1个
private static final int CAPACITY = (1 << COUNT_BITS) - 1;


//ThreadPoolExecutor中定义了线程池的状态,存储在ctl的高三位中,一共有五种
//RUNNING状态,11100000000000000000000000000000
private static final int RUNNING = -1 << COUNT_BITS;
//SHUTDOWN状态:00000000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//STOP状态:00100000000000000000000000000000
private static final int STOP = 1 << COUNT_BITS;
//TIDYING状态:01000000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS;
//TERMINATED状态:01100000000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS;


//通过对ctl的拆解、组合,获取相关的数据
/**
 * 获取ctl的高3位,线程池运行状态
 * @param c 此时的ctl值
 * @return ctl的高3位的int值
 */
private static int runStateOf(int c) {
    //将c的低29位置为0并返回结果
    return c & ~CAPACITY;
}

/**
 * 获取ctl的低29位,线程数量
 * @param c 此时的ctl值
 * @return ctl的低29位的int值
 */
private static int workerCountOf(int c) {
    //将c的高3位置为0并返回结果
    return c & CAPACITY;
}

/**
 * 组合rs和wc,计算ctl新值
 * @param rs 运行状态
 * @param wc 线程数量
 * @return 新ctl的int值
 */
private static int ctlOf(int rs, int wc) {
    //两者与运算
    return rs | wc;
}

//不需要拆解ctl进行数据比较或者更改
/**
 * 运行状态值是否小于指定状态值
 * @param c 此时的ctl
 * @param s 指定状态值
 * @return true 是 false 否
 */
private static boolean runStateLessThan(int c, int s) {
    return c < s;
}

/**
 * 运行状态值是否大于等于指定状态值
 * @param c 此时的ctl
 * @param s 指定状态值
 * @return true 是 false 否
 */
private static boolean runStateAtLeast(int c, int s) {
    return c >= s;
}

/**
 * 运行状态是否是RUNNING
 * @param c 此时的ctl
 * @return true 是 false 否
 */
private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}

/**
 * 尝试CAS的将ctl的WorkerCount线程数量部分自增1
 * @param expect 预期的ctl值
 * @return true 成功 false 失败
 */
private boolean compareAndIncrementWorkerCount(int expect) {
    return ctl.compareAndSet(expect, expect + 1);
}


/**
 * 尝试CAS的将ctl的WorkerCount线程数量部分自减1
 * @param expect 预期的ctl值
 * @return true 成功 false 失败
 */
private boolean compareAndDecrementWorkerCount(int expect) {
    return ctl.compareAndSet(expect, expect - 1);
}

/**
 * 循环尝试CAS的将ctl的WorkerCount线程数量部分自减1,直到成功为止
 * 只有在addWorkerFailed、processWorkerExit以及getTask部分调用
 */
private void decrementWorkerCount() {
    do {
    } while (!compareAndDecrementWorkerCount(ctl.get()));
}

//存放任务的阻塞队列
private final BlockingQueue<Runnable> workQueue;
//worker的集合,用set来存放
private final HashSet<Worker> workers = new HashSet<Worker>();
//历史达到的worker数最大值
private int largestPoolSize;
//当队列满了并且worker的数量达到maxSize的时候,执行具体的拒绝策略
private volatile RejectedExecutionHandler handler;
//超出coreSize的worker的生存时间
private volatile long keepAliveTime;
//常驻worker的数量
private volatile int corePoolSize;
//最大worker的数量,一般当workQueue满了才会用到这个参数
private volatile int maximumPoolSize;

3、线程池状态

1、使用一个ctl原子变量来来同时记录线程池的运行状态(runState,简称rs)和线程池中线程数量(workerCount,简称wc)。int类型转换为二进制之后的最高三位保存线程池的状态,低29位保存线程数量。刚初始化ctl的时候,rs为RUNNING状态,wc为0。
  • RUNNING:-1 << COUNT_BITS,即高3位为111,该状态的线程池会接收新任务,并处理阻塞队列中的任务;
  • SHUTDOWN:0 << COUNT_BITS,即高3位为000,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;
  • STOP:1 << COUNT_BITS,即高3位为001,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;
  • TIDYING:2 << COUNT_BITS,即高3位为010, 所有的任务都已经终止;
  • TERMINATED:3 << COUNT_BITS,即高3位为011, terminated()方法已经执行完成;
2、大小关系:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED
3、转换关系:类似于线程的状态,线程池的状态也可以转换。但是又有不同,线程状态可以循环转换、相互转换,而一旦发生线程池的状态的转换,那么该转换不可逆

在这里插入图片描述

状态名说明转换
RUNNING线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。新建的线程池的初始状态就是RUNNING,并且线程池中的工作线程数量为0。
SHUTDOWN线程池处在SHUTDOWN状态时,不接收新任务,但内部正在执行的任务和队列里等待的任务,会执行完,随后会清理全部工作线程。RUNNING状态的线程池,调用shutdown方法,或者隐式调用了finalize方法(里面有shutdown方法时线程池状态将变成SHUTDOWN。
STOP线程池处在STOP状态时,不接收新任务,不处理已添加的任务(丢弃),并且会中断正在处理的任务,随后会清理全部工作线程。RUNNING或SHUTDOWN状态的线程池,调用shutdownNow方法,线程池状态将变成 STOP。
TIDYING所有的任务已执行完或被终止或被丢弃,ctl记录的workerCount工作线程数量为0,线程池会变为TIDYING状态。接着会执行钩子函数terminated()。SHUTDOWN状态的线程池,当任务队列为空并且线程池工作线程数workerCount为0时,线程池状态就会由SHUTDOWN自动转换为TIDYING状态。
STOP状态的线程池,线程池中工作线程数workerCount为0时,线程池状态就会由STOP自动转换为TIDYING状态。
TERMINATED钩子函数terminated()执行完毕,就变成TERMINATED状态,线程池彻底终止。TIDYING状态的线程池,在接着执行完terminated()之后,线程池状态就会由TIDYING自动转换为 TERMINATED。

4、构造器及七大参数

1、corePoolSize: 线程池核心线程数,它的数量决定了添加的任务是开辟新的线程去执行,还是放到 workQueue任务队列中去。此值必须大于等于1。
2、maximumPoolSize: 最大线程数目,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量。此值必须大于等于1。
3、keepAliveTime:空闲线程存活时间,当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁,针对救急线程。
4、unit: keepAliveTime的单位。
5、workQueue:任务队列,被添加到线程池中,但尚未被执行的任务。
6、threadFactory: 线程工厂,用于创建线程,可以通过线程工厂给每个创建出来的线程设置更有意义的名字,一般用默认即可
7、handler:拒绝策略;当任务太多来不及处理时,如何拒绝任务。
/**
 * 使用给定的初始参数和默认线程工厂和默认的拒绝策略创建一个新的ThreadPoolExecutor
 */
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    //内部调用最多参数的构造器
    //线程工厂传递的Executors的默认实现:Executors.defaultThreadFactory()
    //拒绝策略传递的默认实现:defaultHandler
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

/**
 * 使用给定的初始参数和默认的拒绝策略创建新的ThreadPoolExecutor
 */
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    //内部调用最多参数的构造器
    //拒绝策略传递的默认实现:defaultHandler
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}

/**
 * 使用给定的初始参数和默认的线程工厂创建新的ThreadPoolExecutor。
 */
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    //内部调用最多参数的构造器
    //线程工厂传递的Executors的默认实现:Executors.defaultThreadFactory()
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}

/**
 * 使用给定的初始参数创建新的ThreadPoolExecutor,一共有七个参数
 * @param corePoolSize    核心线程数
 * @param maximumPoolSize 最大线程数
 * @param keepAliveTime   空闲线程等待超时时间
 * @param unit            超时时间单位
 * @param workQueue       阻塞任务队列
 * @param threadFactory   线程工厂
 * @param handler         拒绝策略
 */
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.acc = System.getSecurityManager() == null ?
        null :
    AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

5、线程池的工作原理

1、在创建了线程池后,等待提交过来的任务请求。
2、当调用execute()方法添加一个请求任务时,线程池会做如下判断:
  • 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务。
  • 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入BlockingQueue即阻塞队列中。
  • 如果阻塞队列满了,并且正在运行的线程数量小于maximumPoolSize,那么还需要创建maximumPoolSize - corePoolSize数量的线程来救急,并且立即执行这个任务
  • 如果阻塞队列满了,且正在运行的线程数量达到 maximumPoolSize,仍然有新任务,那么线程池会执行拒绝策略
3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4、当一个线程的空闲时间超过keepAliveTime时,线程池会做如下判断:
  • 如果当前运行的线程数大于corePoolSize,那么这个线程将会停掉。
  • 所以线程池中所有任务执行完成后,最终会收缩到corePoolSize的大小

在这里插入图片描述

6、ThreadFactory线程工厂

1、线程池统一通过ThreadFactory创建新线程,可以说是工厂模式的应用。默认使用Executors.defaultThreadFactory工厂,该工厂创建的线程全部位于同一个ThreadGroup中,并且具有pool-N-thread-M的线程命名(N表示线程池工厂编号,M表示一个工厂创建的线程编号,都是自增的)和非守护进程状态。
2、通过实现ThreadFactory接口,可以自定义线程的名称,线程组,优先级,守护进程状态等。如果ThreadFactory在通过从new Thread返回null时未能创建线程,则执行程序将继续,但可能无法执行任何任务。

源码分析:

static class DefaultThreadFactory implements ThreadFactory {
    //线程工厂编号计数器
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    //线程组
    private final ThreadGroup group;
    //线程计数器
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    //线程名称前缀
    private final String namePrefix;

    //构造方法
    DefaultThreadFactory() {
        //安全管理器
        SecurityManager s = System.getSecurityManager();
        //如果安全管理器不为空,则从安全管理器中获取一个线程组,否则获取当前线程所属的线程组
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        //线程名称前缀,pool-线程池工厂编号-thread-工厂创建的线程编号
        namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
    }
    
	//创建一个新的线程
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        //判断当前线程是否为守护线程,如果为守护线程,则设置成用户线程(非守护线程)
        if (t.isDaemon())
            t.setDaemon(false);
        //判断当前线程的优先级,如果不为NORM_PRIORITY,则设置优先级为NORM_PRIORITY
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        //返回线程对象
        return t;
    }
}

自定义线程工厂:

/**
 * @Date: 2022/7/1
 * 自定义线程工厂
 */
public class CustomizeThreadFactory {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 100L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                //自定义线程工厂
                r -> {
                    System.out.println("创建 " + r.hashCode() + " 线程");
                    return new Thread(r,"customize-pool-" + r.hashCode());
                }, new ThreadPoolExecutor.CallerRunsPolicy());
        try {
            for (int i = 0; i < 6; i++) {
                final int no = i;
                pool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 处理任务 " + no);
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }
}
/**
 * 运行结果:
 * 创建 1650967483 线程
 * 创建 87285178 线程
 * 创建 610998173 线程
 * customize-pool-1650967483 处理任务 0
 * customize-pool-87285178 处理任务 1
 * customize-pool-610998173 处理任务 5
 * customize-pool-610998173 处理任务 2
 * customize-pool-87285178 处理任务 4
 * customize-pool-1650967483 处理任务 3
 */

7、提交任务方法

1、ThreadPoolExecutor继承AbstractExecutorService类,AbstractExecutorService类实现ExecutorService接口中的方法,因此一些方法可以在ThreadPoolExecutor中直接被调用。
//执行任务
void execute(Runnable command);

//提交任务task,用返回值Future获得任务执行结果
<T> Future<T> submit(Callable<T> task);

//提交tasks中所有任务
<T> List<Future<T>> invokeAll(Collection<? entends Callable<T>> tasks) throws InterruptedException;

// 提交tasks中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? entends Callable<T>> tasks,long timeout,TimeUnit unit) throws InterruptedException;

// 提交tasks中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其他任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException,ExecutionException;

// 提交tasks中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout,TimeUnit unit) throws InterruptedException,ExecutionException;

带有返回值的任务:

/**
 * @Date: 2022/7/2
 * 带有返回值任务测试
 */
public class WithReturnValueTaskTest {
    public static void main(String[] args) {
        //corePoolSize设置为1,maximumPoolSize设置为2,队列使用无界队列,拒绝策略为AbortPolicy
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 100L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        //接收返回值
        List<Future<?>> futures = new ArrayList<>();
        try {
            for (int i = 0; i < 3; i++) {
                final int no = i;
                Future<Integer> future = pool.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + " 处理任务 " + no);
                    return no;
                });
                futures.add(future);
            }
            //查看结果
            for (Future<?> future : futures) {
                //get方法会阻塞主线程
                System.out.println("返回结果:" + future.get());
            }
            System.out.println("执行完成...");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }
}
/**
 * 运行结果如下:
 * pool-1-thread-1 处理任务 0
 * 返回结果:0
 * pool-1-thread-1 处理任务 1
 * 返回结果:1
 * pool-1-thread-1 处理任务 2
 * 返回结果:2
 * 执行完成...
 */

invokeAll执行所有任务:

/**
 * @Date: 2022/7/2
 * invokeAll执行所有任务测试
 */
public class invokeAllTaskTest {
    public static void main(String[] args) {
        //corePoolSize设置为1,maximumPoolSize设置为2,队列使用无界队列,拒绝策略为AbortPolicy
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 100L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        try {
            //接收返回值
            List<Future<Integer>> futures = pool.invokeAll(Arrays.asList(
                    () -> {
                        System.out.println(Thread.currentThread().getName() + " 处理任务 ");
                        return 1;
                    },
                    () -> {
                        System.out.println(Thread.currentThread().getName() + " 处理任务 ");
                        return 2;
                    },
                    () -> {
                        System.out.println(Thread.currentThread().getName() + " 处理任务 ");
                        return 3;
                    }));
            //查看结果
            for (Future<Integer> future : futures) {
                //get方法会阻塞主线程
                System.out.println("返回结果:" + future.get());
            }
            System.out.println("执行完成...");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭线程池
            pool.shutdown();
        }
    }
}
/**
 * 运行结果如下:
 * pool-1-thread-1 处理任务
 * pool-1-thread-1 处理任务
 * pool-1-thread-1 处理任务
 * 返回结果:1
 * 返回结果:2
 * 返回结果:3
 * 执行完成...
 */

invokeAny谁先执行完就返回:

/**
 * @Date: 2022/7/2
 * invokeAny测试
 */
public class invokeAnyTaskTest {
    public static void main(String[] args) {
        //corePoolSize设置为1,maximumPoolSize设置为2,队列使用无界队列,拒绝策略为AbortPolicy
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 100L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        try {
            //接收返回值
            Integer result = pool.invokeAny(Arrays.asList(
                    () -> {
                        System.out.println(Thread.currentThread().getName() + " 处理任务 ");
                        Thread.sleep(3000);
                        return 1;
                    },
                    () -> {
                        System.out.println(Thread.currentThread().getName() + " 处理任务 ");
                        Thread.sleep(2000);
                        return 2;
                    },
                    () -> {
                        System.out.println(Thread.currentThread().getName() + " 处理任务 ");
                        Thread.sleep(1000);
                        return 3;
                    }));
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }
}
/**
 * 运行结果如下:
 * pool-1-thread-1 处理任务
 * pool-1-thread-1 处理任务
 * 1
 */

8、关闭线程池

1、关闭线程池的两种方法:
  • shutdown:方法会将线程池的状态设置为SHUTDOWN,线程池进入这个状态后,就拒绝再接受任务,然后会将剩余的任务全部执行完。
  • shutdownNow:做的比较绝,它先将线程池状态设置为STOP,然后拒绝所有提交的任务。最后中断正在运行中的worker,然后清空任务队列。

shutdown源码:

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    //加锁
    mainLock.lock();
    try {
        //检查是否可以关闭线程
        checkShutdownAccess();
        //设置线程池状态为SHUTDOWN
        advanceRunState(SHUTDOWN);
        //尝试中断worker
        interruptIdleWorkers();
        //预留方法,留给子类实现
        onShutdown();
    } finally {
        //释放锁
        mainLock.unlock();
    }
    tryTerminate();
}

private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        //遍历所有的worker
        for (Worker w : workers) {
            Thread t = w.thread;
            //先尝试调用w.tryLock(),如果获取到锁,就说明worker是空闲的,就可以直接中断它
            //注意:worker自己本身实现了AQS同步框架,然后实现的类似锁的功能
            //它实现的锁是不可重入的,所以如果worker在执行任务的时候,会先进行加锁,这里tryLock()就会返回false
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

shutdownNow源码:

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        //检查是否可以关闭线程
        checkShutdownAccess();
        //检测权限
        advanceRunState(STOP);
        //中断所有的worker
        interruptWorkers();
        //清空任务队列
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        //遍历所有worker,然后调用中断方法
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}

四、线程池参数之workQueue任务队列

1、概述

1、任务队列用于存放提交的来不及执行的任务,一定是一个阻塞队列BlockingQueue,JDK自带的阻塞队列如下:
  • ArrayBlockingQueue:有界阻塞队列
  • LinkedBlockingQueue:无界阻塞队列
  • LinkedBlockingDeque:无界阻塞双端队列
  • SynchronousQueue:没有容量的阻塞队列
  • DelayQueue:支持延时操作的无界阻塞队列
  • PriorityBlockingQueue:任务具有优先级的无界阻塞队列
  • LinkedTransferQueue:JDK1.7的新无界阻塞队列
2、根据常用阻塞队列的类型,任务队列一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列,当然还有其他的类型。

2、直接提交队列

1、队列设置为SynchronousQueue,它是一个特殊的BlockingQueue,它没有容量,每一个入队操作都需要匹配一个等待的出队操作或者等待被后来的出队操作匹配才能返回,出队操作同理。
2、若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,如果使用SynchronousQueue队列,新任务不会被保存在任务队列中,总是会马上被执行。如果此时线程数量小于maximumPoolSize,则尝试创建新的线程执行;如果达到maximumPoolSize设置的最大值,则传递给执行拒绝策略去执行
3、采用SynchronousQueue作为任务队列的线程池不能缓存任务,一个任务要么被执行要么被拒绝策略处理,这就是“直接提交”的由来。
/**
 * @Date: 2022/7/1
 * 直接提交队列测试
 */
public class ThreadPoolTest7 {
    public static void main(String[] args) {
        //maximumPoolSize设置为2,拒绝策略为AbortPolicy,直接抛出异常
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 100L, TimeUnit.SECONDS,
                            new SynchronousQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        try {
            for (int i = 0; i < 6; i++) {
                final int no = i;
                pool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 处理任务 " + no);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }
}
/**
 * 运行结果:
 * pool-1-thread-2 处理任务 1
 * pool-1-thread-1 处理任务 0
 * java.util.concurrent.RejectedExecutionException: Task com.itan.pool.ThreadPoolTest7$$Lambda$1/777874839@6433a2 rejected from java.util.concurrent.ThreadPoolExecutor@5910e440[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]
 * 	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
 * 	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
 * 	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
 * 	at com.itan.pool.ThreadPoolTest7.main(ThreadPoolTest7.java:18)
 */

3、有界任务队列

1、阻塞队列设置为ArrayBlockingQueue,它是一个特殊的BlockingQueue,它必须设置容量,作为有界任务队列。
2、若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,如果使用ArrayBlockingQueue队列,则会将新的任务加入到等待队列中,如果达到队列的容量,则继续添加的任务会被处理。如果此时线程数量小于maximumPoolSize,则尝试创建新的线程执行;如果达到maximumPoolSize设置的最大值,则传递给执行拒绝策略去执行
3、采用ArrayBlockingQueue作为任务队列的线程池可以缓存任务,是较为常见的任务队列。
/**
 * @Date: 2022/7/1
 * 有界队列测试
 */
public class ThreadPoolTest8 {
    public static void main(String[] args) {
        //maximumPoolSize设置为2,拒绝策略为AbortPolicy,直接抛出异常
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 100L, TimeUnit.SECONDS,
                            new ArrayBlockingQueue<Runnable>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        try {
            for (int i = 0; i < 6; i++) {
                final int no = i;
                pool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 处理任务 " + no);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }
}

4、无界任务队列

1、阻塞队列设置为LinkedBlockingQueue,它是一个特殊的BlockingQueue,它虽然可以设置容量,但是不设置容量就作为无界任务队列。
2、若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,如果使用LinkedBlockingQueue队列,并且没有指定容量,那么可以无限制的保存添加的新任务(实际上最大容量为Integer.MAX_VALUE,但是基本上达不到就会抛出内存溢出异常)
3、采用LinkedBlockingQueue作为任务队列的线程池可以无限缓存任务,这时,设置maximumPoolSize参数是无效的,当线程池的线程数达到corePoolSize后就不会再增加了,此时需要注意内存资源耗尽的问题
4、由于LinkedBlockingQueue也可以设置容量,因此也可以作为有界任务队列,并且由于它采用了两把锁,性能好于采用一把锁的ArrayBlockingQueue。
/**
 * @Date: 2022/7/1
 * 无界队列测试
 */
public class ThreadPoolTest9 {
    public static void main(String[] args) {
        //maximumPoolSize设置为2,拒绝策略为AbortPolicy,直接抛出异常
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 100L, TimeUnit.SECONDS,
                            new LinkedBlockingQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        try {
            for (int i = 0; i < 6; i++) {
                final int no = i;
                pool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 处理任务 " + no);
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }
}
/**
 * 运行结果:并没有创建救急线程来运行后面的任务
 * pool-1-thread-1 处理任务 0
 * pool-1-thread-1 处理任务 1
 * pool-1-thread-1 处理任务 2
 * pool-1-thread-1 处理任务 3
 * pool-1-thread-1 处理任务 4
 * pool-1-thread-1 处理任务 5
 */

5、优先级任务队列

1、阻塞队列设置为PriorityBlockingQueue,它是一个特殊的BlockingQueue,可以为任务设置优先级,优先最高的任务将会先出队列被执行,它要求任务可比较大小(需要实现Comparator接口)。
2、使用PriorityBlockingQueue队列,通常设置corePoolSize为0,这样新来的任务将会直接进入PriorityBlockingQueue,如果没有指定容量,那么可以无限制的保存添加的新任务(由于底层是数组,实际上最大容量为Integer.MAX_VALUE,但是基本上达不到就会抛出内存溢出异常)
3、采用PriorityBlockingQueue作为任务队列的线程池可以无限缓存任务,如果没有设置容量,maximumPoolSize参数是无效的,当线程池的线程数达到corePoolSize后就不会再增加了,此时需要注意内存资源耗尽的问题
/**
 * @Date: 2022/7/1
 * 优先级队列测试
 */
public class ThreadPoolTest10 {
    public static void main(String[] args) {
        //corePoolSize设置为0,目的是防止第一个任务被直接执行
        //maximumPoolSize设置为2,拒绝策略为AbortPolicy,直接抛出异常
        ThreadPoolExecutor pool = new ThreadPoolExecutor(0, 2, 100L, TimeUnit.SECONDS,
                            new PriorityBlockingQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        try {
            //循环从priority=0开始添加任务,即最先添加的任务优先级最低
            for (int i = 0; i < 6; i++) {
                pool.execute(new RunnableTask(i));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }

    /**
     * 需要实现Comparable接口,可比较大小
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    static class RunnableTask implements Runnable, Comparable<RunnableTask> {
        //任务优先级大小
        private Integer priority;

        /**
         * 当前对象和其他对象做比较,当前优先级大就返回-1,优先级小就返回1,值越小优先级越高
         * @param o
         * @return
         */
        @Override
        public int compareTo(RunnableTask o) {
            return this.priority > o.priority ? -1 : 1;
        }

        @Override
        public void run() {
            //让线程阻塞,使后续任务进入缓存队列
            try {
                Thread.sleep(1000L);
                System.out.println("priority " + this.priority + " ThreadName " + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
/**
 * 运行结果:可以发现最后添加的任务(优先级最高)最先被执行
 * priority 5 ThreadName pool-1-thread-1
 * priority 4 ThreadName pool-1-thread-1
 * priority 3 ThreadName pool-1-thread-1
 * priority 2 ThreadName pool-1-thread-1
 * priority 1 ThreadName pool-1-thread-1
 * priority 0 ThreadName pool-1-thread-1
 */

五、线程池参数之RejectedExecutionHandler拒绝策略

1、概述

1、对于正在执行的线程数大于等于maxmumPoolSize以及workQueue容量已满时提交的任务,或者线程池正在关闭时的新提交的任务,线程池将会执行拒绝策略,即这些任务都直接被非线程池线程处理了。
2、JDK内置的拒绝策略实现有4种,都位于ThreadPoolExecutor内部作为内部类。需要注意的是拒绝策略是由调用execute或者submit方法的线程去执行的,而不是线程池的线程去执行。默认情况下是AbortPolicy,表示无法处理新任务时抛出异常

在这里插入图片描述

2、AbortPolicy策略

1、调用线程直接直接抛出RejectedException异常,阻止系统正常运行,线程池的默认策略

源码分析:

/**
 * 默认的拒绝策略,可以看到就是AbortPolicy策略
 */
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();

/**
 * 调用者抛出RejectedExecutionException异常的拒绝策略
 */
public static class AbortPolicy implements RejectedExecutionHandler {
    
    public AbortPolicy() {}

    /**
     * 总是抛出RejectedExecutionException
     * @param r 任务
     * @param e 线程池
     * @throws RejectedExecutionException异常
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}
示例:corePoolSize设置为1,maximumPoolSize设置为2,队列容量设置为3,拒绝策略为AbortPolicy(直接抛出异常)

在这里插入图片描述

/**
 * @Date: 2022/7/2
 * 直接抛出异常策略测试
 */
public class AbortPolicyTest {
    public static void main(String[] args) {
        //corePoolSize设置为1,maximumPoolSize设置为2,队列容量设置为3,拒绝策略为AbortPolicy(直接抛出异常)
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 100L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        try {
            for (int i = 0; i < 6; i++) {
                final int no = i;
                pool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 处理任务 " + no);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }
}

3、CallerRunsPolicy策略

1、如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;如果线程池关闭,任务将被静默丢弃。需要注意可能会阻塞调用线程线程

源码分析:

/**
 * 该策略就直接在调用者线程中,运行当前被丢弃的任务
 */
public static class CallerRunsPolicy implements RejectedExecutionHandler {
    
    public CallerRunsPolicy() {}

    /**
     * 在调用方的线程中执行任务r,除非执行器已关闭(SHUTDOWN以及之后的状态),在这种情况下,任务被丢弃
     * @param r 任务
     * @param e 线程池
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        //判断线程池是否没有被关闭
        if (!e.isShutdown()) {
            //直接通过调用线程运行run方法
            r.run();
        }
    }
}

示例:

/**
 * @Date: 2022/7/2
 * 调用者线程运行拒绝任务的测试
 */
public class CallerRunsPolicyTest {
    public static void main(String[] args) {
        //corePoolSize设置为1,maximumPoolSize设置为2,队列容量设置为3,拒绝策略为CallerRunsPolicy(调用者线程运行拒绝任务)
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 100L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
        try {
            for (int i = 0; i < 6; i++) {
                final int no = i;
                pool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 处理任务 " + no);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }
}
/**
 * 运行结果如下:可以看到任务5被main线程直接运行
 * main 处理任务 5
 * pool-1-thread-2 处理任务 4
 * pool-1-thread-1 处理任务 0
 * pool-1-thread-2 处理任务 1
 * pool-1-thread-1 处理任务 2
 * pool-1-thread-2 处理任务 3
 */

4、DiscardOldestPolicy策略

1、该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交当前任务

源码分析:

/**
 * 如果线程池被关闭,那么直接丢弃任务。
 * 否则丢弃队列中最老的一个任务,也就是即将被执行的一个任务,并尝试再次提交当前任务
 */
public static class DiscardOldestPolicy implements RejectedExecutionHandler {

    public DiscardOldestPolicy() {}

    /**
     * 如果线程池被关闭,那么直接丢弃任务。
     * 否则丢弃队列中最老的一个任务,也就是即将被执行的一个任务,并尝试再次提交当前任务
     * @param r 任务
     * @param e 线程池
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        //判断线程池是否没有被关闭
        if (!e.isShutdown()) {
            //将队头任务移除队列丢弃
            e.getQueue().poll();
            //尝试再次提交任务
            e.execute(r);
        }
    }
}

示例:

/**
 * @Date: 2022/7/2
 * 丢弃最老的任务策略测试
 */
public class DiscardOldestPolicyTest {
    public static void main(String[] args) {
        //corePoolSize设置为1,maximumPoolSize设置为2,队列容量设置为3,拒绝策略为DiscardOldestPolicy(丢弃最老任务,并提交当前任务)
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 100L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());
        try {
            for (int i = 0; i < 6; i++) {
                final int no = i;
                pool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 处理任务 " + no);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }
}
/**
 * 运行结果如下:首先任务0被核心线程直接执行,任务1,2,3进入队列;
 *             任务队列已满,线程数小于maximumPoolSize,任务4被救急线程执行;
 *             任务5进来后,正在运行的线程数达到maximumPoolSize,且队列已满,
 *             这时,就将队列中最先进去的任务丢弃(也就是任务1),并把任务5放入
 *             队列中并提交任务。
 * pool-1-thread-1 处理任务 0
 * pool-1-thread-2 处理任务 4
 * pool-1-thread-2 处理任务 2
 * pool-1-thread-2 处理任务 3
 * pool-1-thread-1 处理任务 5
 */

5、DiscardPolicy策略

1、该策略会直接丢弃任务,不予任何处理,也不抛出异常。当然使用此策略,业务场景中需要允许任务的丢失

源码分析:

/**
 * 丢弃无法处理的任务,不予任何处理。如果允许任务丢失,这是最好的一种方案。
 */
public static class DiscardPolicy implements RejectedExecutionHandler {

    public DiscardPolicy() {}

    /**
     * 丢弃无法处理的任务,不予任何处理。如果允许任务丢失,这是最好的一种方案。
     * @param r 任务
     * @param e 线程池
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        //什么也不做
    }
}

示例:

/**
 * @Date: 2022/7/2
 * 直接丢弃任务策略测试
 */
public class DiscardPolicyTest {
    public static void main(String[] args) {
        //corePoolSize设置为1,maximumPoolSize设置为2,队列容量设置为3,拒绝策略为DiscardOldestPolicy(丢弃最老任务,并提交当前任务)
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 100L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());
        try {
            for (int i = 0; i < 6; i++) {
                final int no = i;
                pool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 处理任务 " + no);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }
}
/**
 * 运行结果如下:首先任务0被核心线程直接执行,任务1,2,3进入队列;
 *             任务队列已满,线程数小于maximumPoolSize,任务4被救急线程执行;
 *             任务5进来后,正在运行的线程数达到maximumPoolSize,且队列已满,
 *             这时,直接将任务5丢弃,所以没有看到任务执行
 * pool-1-thread-1 处理任务 0
 * pool-1-thread-2 处理任务 4
 * pool-1-thread-1 处理任务 1
 * pool-1-thread-2 处理任务 2
 * pool-1-thread-1 处理任务 3
 */

6、自定义拒绝策略

1、以上内置拒绝策略均实现了RejectedExecutionHandler接口,若以上策略仍无法满足实际需要,完全可以自己实现RejectedExecutionHandler接口,并且实现rejectedExecution方法就可以了。具体的逻辑就在rejectedExecution方法里去定义就OK了。这是一种设计模式—策略模式!
/**
 * @Date: 2022/7/2
 * 自定义拒绝策略
 */
public class CustomizeRejectedPolicy {
    public static void main(String[] args) {
        //corePoolSize设置为1,maximumPoolSize设置为2,队列容量设置为3,拒绝策略为DiscardOldestPolicy(丢弃最老任务,并提交当前任务)
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 100L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),
                //自定义拒绝策略
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.out.println(r.toString() + " 执行了拒绝策略");
                    }
                });
        try {
            for (int i = 0; i < 6; i++) {
                final int no = i;
                pool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 处理任务 " + no);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }
}
/**
 * 运行结果:
 * com.itan.pool.CustomizeRejectedPolicy$$Lambda$1/2108649164@21b8d17c 执行了拒绝策略
 * pool-1-thread-2 处理任务 4
 * pool-1-thread-1 处理任务 0
 * pool-1-thread-2 处理任务 1
 * pool-1-thread-1 处理任务 2
 * pool-1-thread-2 处理任务 3
 */

7、如何合理配置线程池

1、代码查看CPU核心数:Runtime.getRuntime().availableProcessors()
2、看业务是CPU密集型还是IO密集型的,这两种不一样,来决定线程池线程数的最佳合理配置数。
3、CPU密集型运算:
  • CPU密集是指任务需要大量的运算,而没有阻塞,CPU一直全速运行,CPU密集任务只有在真正的多核CPU上才可能通过多线程得到加速。
  • CPU密集型任务配置尽可能少的线程数量:CPU核数 + 1个线程的线程池
4、IO密集型运算:
  • IO密集是指任务需要大量的IO,即大量的阻塞。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程。
  • 在单线程上运行IO密集型的任务会导致大量的CPU运算能力浪费在等待上,所以在IO密集型任务中使用多线程可以加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费的阻塞时间。
  • 参考公式:CPU核数 / 1 - 阻塞系数(通常0.8~0.9)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值