Java线程池--七个参数详解和线程的周期任务

对于java开发,多线程一直都是面试和实际开发编程当中的一个重灾区,很多同学对于这一块都用的很少,甚至不清楚它的具体概念。

本文主要基于Executor线程池接口,重点介绍它其中的两个子接口ExecutorServiceScheduledExecutorService 。并通过这两个子接口讲解 ThreadPoolExecutor 的七个参数的含义和 ScheduledThreadPoolExecutor如何实现线程的周期任务。


一 Executor 线程池接口

1.1 基本概念

我们都知道,正常情况下,代码的执行都是单线程的,也就是一个线程从方法的开始到结尾。如果代码中间某一句运行需要大量的时间,那么也只能等到其运行完成才能做接下来的操作。这个时候就想到了在不影响原逻辑的结果情况下,另起一条线程去执行该段代码块。而主线程直接执行下面部分。

线程池就是线程的集合,线程池的优势是 方便集中管理线程,以实现线程的重用,降低资源消耗,提高响应速度等 线程池中的线程可用于执行异步任务,单个的线程既是工作单元也是执行机制

1.2 继承体系

Executor是线程池的顶级父接口,其中只有一个execute方法,用于执行线程。

Executor接口中的方法

在这里插入图片描述

下面我们来看看Executor的继承体系

Executor线程池的继承体系(实线为extend,虚线为implement)

在这里插入图片描述

实际开发当中常用的线程池实现为 ThreadPoolExecutorScheduledThreadPoolExecutor 下文会做详细的讲解。

二 ExecutorService基本线程池接口

2.1 基本概念

ExecutorServiceExecutor 的子接口,它在 Executor 基本的 execute 提交线程的方法上还新加了许多诸如 停止线程池中线程、阻塞某个线程、提交线程集合等操作。

关于ExecutorService最重要的一个实现就是 ThreadPoolExecutorThreadPoolExecutor 是最原始、也是最推荐的手动创建线程池的方式,关于创建它时 7 个参数 也是面试的 “常青树”。

ThreadPoolExecutor的继承关系

所有的父类接口(extend)已知父类实现(implement)
ExecutorAbstractExecutorService
ExecutorService

2.2 ThreadPoolExecutor的七个参数

ThreadPoolExecutor的有参构造

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JsV2QWMp-1676776300618)(/Users/tinghuiliu/Library/Application Support/typora-user-images/image-20230219101525571.png)]

各个参数的意思

入参名含义
corePoolSize线程中的核心线程数。是线程池会维护的最小线程数量,即使这些线程在空闲状态下,
也不会被销毁
maximumPoolSize线程池最大线程量。当线程数达到了corePoolSize后,如果继续有任务提交到线程池,
会被缓存到任务队列。如果队列也满了,就会创建一个新的线程来处理。而新创建的线
程数量+corePoolSize总和由该参数决定
keepAliveTime空闲线程存活时间。一个线程如果处于空闲状态,并且当前线程数量大于corePoolSize,
这个空闲的线程则会被销毁,销毁的事件由该参数决定
unit空闲线程存活时间单位。可以设置毫秒,秒,小时等
workQueue工作队列。新的任务被提交后,会先进入到此工作队列,任务调度时再从队列中取出任
务,jdk提供了四种工作队列:ArrayBlockingQueue、LinkedBlockingQuene、
SynchronousQuene、PriorityBlockingQueue
threadFactory线程工厂。可以用来设定线程名等
handler拒绝策略。工作队列中的任务已到达最大限制,并且线程池中的线程数量也到达
最大限制,如果这时有新的任务提交,如何拒绝。
jdk提供了四种拒绝策略:CallerRunsPolicy、
AbortPolicy、DiscardPolicy、DiscardOldestPolicy

2.2.1 四种工作队列

ArrayBlockingQueue

基于数组的有界阻塞队列,按FIFO(先进先出)排序 。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。

LinkedBlockingQuene

基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO(先进先出)排序。 由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而基本不会去创建新线程直到maxPoolSize(很难达到Interger.MAX这个数),因此使用该工作队列时,参数maxPoolSize其实是不起作用的。

SynchronousQuene

一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。

PriorityBlockingQueue

具有优先级的无界阻塞队列,优先级通过参数Comparator实现。

2.2.2 四种拒绝策略

CallerRunsPolicy

该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。

AbortPolicy

该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。

DiscardPolicy

该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。

DiscardOldestPolicy

该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列

三 ScheduledExecutorService 定时周期线程池接口

3.1 基本概念

ScheduledExecutorService 可以调度任务再给定的延迟时间之后再运行,或者定期执行。从而 创建具有各种延迟效果的任务,并返回可用于取消或者检查执行任务的对象(即ScheduledFuture对象)。

ScheduledExecutorService的继承关系

所有的父类接口(extend)已知实现(implement)
ExecutorScheduledThreadPoolExecutor
ExecutorService

关于ScheduledExecutorService接口,我们重点要学习了解的它的实现是 ScheduledThreadPoolExecutor,在下文的示例中我饿会具体的介绍几种它的具体用法,我们先来看看它都包含那些方法吧。

3.2 方法介绍

在介绍ScheduledExecutorService的方法之前,我们先来了解一下 ScheduledFuture 这个类的作用

ScheduleFuture的作用

ScheduleFuture对象简单来说其实就是在执行ScheduledExecutorService方法后,返回给用户的一个对象,通过这个对象,用户方便对执行的任务进行检查或者取消 。 ScheduleExecutorService的四个方法在执行完毕后都会返回这个对象。

ScheduleExeutorService 的方法

返回值方法和描述
ScheduledFutureschedule(Callable callable, long delay, TimeUnit unit)
在给定延迟时间后,只执行一次有返回值的任务操作。
ScheduledFuture<?>schedule(Runnable command, long delay, TimeUnit unit)
在给定延迟时间后,只执行一次无返回值的任务操作
ScheduledFuture<?>scheduleAtFixedRate(Runnable command, long initialDelay,
long period, TimeUnit unit)
固定比例的周期执行,即在上一个任务结束后,开始指定时间的计时,
每个任务的开始时间与上一个任务的结束时间之间的比例是固定的
ScheduledFuture<?>scheduleWithFixedDelay(Runnable command, long initialDelay,
long delay, TimeUnit unit)
固定延迟的周期执行, 即不管上一个任务是否结束,只要上一个任务
开始,就进行延迟的计时。
到了设置的延迟时间,就开始下一次任务。
每个任务的开始时间是固定的,delay+initialDelay*当前任务数-1
前面的任务执行的慢,不会影响后面的任务开始

3.3 ScheduledThreadPoolExecutor演示

Computable (函数式接口 , 可用于Lambda入参,加入停止线程的判断条件)

package com.chatapp;

/**
 * 函数式接口,可用于自己编写的线程池方法入参判断是否退出定时任务的条件,返回true退出线程
 */
@FunctionalInterface
public interface Computable {
    public abstract boolean compute();
}

ScheduledThreadPoolUtil

package com.chatapp;
 
import org.apache.commons.lang3.concurrent.BasicThreadFactory;

import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
/**
 * 定时线程池工具类
 * 包含了三种方法
 * 1. 按输入的指定时间执行完 固定的时长后 退出
 *     scheduleAtFixedRateByTime(原理为 scheduleAtFixedRate + 输入一个指定时间 判断到了这个时间就remove )
 * 2. 按输入的指定次数执行后退出
 *    scheduleDelayByNumber (原理为 schedule +  输入一个次数 for 循环 )
 * 3. 自定义条件 满足条件后 退出
 *   scheduleAtFixedRateByCompute (原理为 scheduleAtFixedRate + 自定义Computable函数,满足true后 remove )
 */
public class ScheduledThreadPoolUtil {

    //核心线程数
    private static final int CORE_POOL_SIZE =  5;
 
    /** 监听剔除定时任务间隔时间*/
    private static final long REJECT_FUTURE_INTERVAL_TIME = 1;
 
    private static volatile ScheduledThreadPoolExecutor THREAD_POOL;
 
  	//双重检查锁创建一个单例对象
    private static ScheduledThreadPoolExecutor getInstance() {
        if (THREAD_POOL == null) {
            synchronized (ScheduledThreadPoolUtil.class) {
                if (THREAD_POOL == null || THREAD_POOL.isShutdown()) {
                  //使用基础的线程工厂创建 一个线程池 BasicThreadFactory
                    THREAD_POOL = new ScheduledThreadPoolExecutor(CORE_POOL_SIZE,
                                new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build());
                }
            }
        }
        return THREAD_POOL;
    }
 
    /**
     * 固定比例延迟执行,适合执行时间比“间隔”短的任务
     * (即执行的Runable 所需时间较短 就用 scheduleAtFixedRate 等上一次执行完了,再开始延迟的计时 )
     * 即下一次任务开始的时间为:上一次任务开始时间 + period时间
     * 如果任务执行的时间比period长的话,会导致该任务延迟执行,不会同时执行!
     * 如果任务执行过程抛出异常,后续不会再执行该任务!
     *
     * @param command
     *              执行体
     * @param initialDelay
     *              初始延迟执行时间
     * @param period
     *              开始时间的间隔为period,即“固定间隔”执行
     * @param executionTotal
     *              定时任务执行时长
     * @param unit
     *              时间单位
     */
    public static void scheduleAtFixedRateByTime(Runnable command, long initialDelay, long period, long executionTotal, TimeUnit unit) {
        ScheduledThreadPoolExecutor poolExecutor = ScheduledThreadPoolUtil.getInstance();
        ScheduledFuture<?> scheduledFuture = poolExecutor.scheduleAtFixedRate(command, initialDelay, period, unit);
        Long start = System.nanoTime();
        for(;;) {
            try {
                TimeUnit.MILLISECONDS.sleep(REJECT_FUTURE_INTERVAL_TIME);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (System.nanoTime() - start >= unit.toNanos(executionTotal)) {
                poolExecutor.remove((Runnable) scheduledFuture);
                break;
            }
        }
    }
 
    /**
     * 固定时间延迟执行,适合 执行时间 比“间隔”长的任务
     * (即执行的Runable 所需时间较长 就用 scheduleWithFixedDelay 让上一次开始后,就开始间隔计时 )
     * 在每一次执行终止和下一次执行开始之间都存在给定的延迟delay时间
     * 即下一次任务开始的时间为:上一次任务结束时间(而不是开始时间) + delay时间
     * 如果任务执行过程抛出异常,后续不会再执行该任务!
     *
     * @param command
     *              执行体
     * @param initialDelay
     *              初始延迟执行时间
     * @param delay
     *              开始时间的间隔为period,即“固定间隔”执行
     * @param executionTotal
     *              定时任务执行时长
     * @param unit
     *              时间单位
     */
    public static void scheduleWithFixedDelayByTime(Runnable command, long initialDelay, long delay, long executionTotal, TimeUnit unit) {
        ScheduledThreadPoolExecutor poolExecutor = ScheduledThreadPoolUtil.getInstance();
        ScheduledFuture<?> scheduledFuture = poolExecutor.scheduleWithFixedDelay(command, initialDelay, delay, unit);
 
        Long start = System.nanoTime();
        for(;;) {
            try {
                TimeUnit.MILLISECONDS.sleep(REJECT_FUTURE_INTERVAL_TIME);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (System.nanoTime() - start >= unit.toNanos(executionTotal)) {
                scheduledFuture.cancel(true);
                break;
            }
        }
    }
 
    /**
     * 固定间隔延迟执行
     * @param command
     *              执行体
     * @param initialDelay
     *              初始延迟执行时间
     * @param delay
     *              开始时间的间隔为period,即“固定间隔”执行
     * @param number
     *              执行多少次后退出
     * @param unit
     */
    public static void scheduleDelayByNumber(Runnable command, long initialDelay, long delay, int number, TimeUnit unit) {
        ScheduledThreadPoolExecutor poolExecutor = ScheduledThreadPoolUtil.getInstance();
        long everyDelay = initialDelay;
        for (int i = 0; i < number; i++) {
            poolExecutor.schedule(command, everyDelay, unit);
            everyDelay += delay;
        }
    }
 
    /**
     * 根据computable 计算状态是否退出, Computable.compute()返回 true 退出
     * 适合执行时间比“间隔”短的任务
     * @param command
     *              执行体
     * @param initialDelay
     *              初始延迟执行时间
     * @param period
     *              开始时间的间隔为period,即“固定间隔”执行
     * @param computable
     *              计算属性, 是一个随时间可变属性
     * @param unit
     */
    public static void scheduleAtFixedRateByCompute(Runnable command, long initialDelay, long period, Computable computable, TimeUnit unit) {
        ScheduledThreadPoolExecutor poolExecutor = ScheduledThreadPoolUtil.getInstance();
        ScheduledFuture<?> scheduledFuture = poolExecutor.scheduleAtFixedRate(command, initialDelay, period, unit);
        for(;;) {
            try {
                TimeUnit.MILLISECONDS.sleep(REJECT_FUTURE_INTERVAL_TIME);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (computable.compute()) {
                poolExecutor.remove((Runnable) scheduledFuture);
                break;
            }
        }
    }
 
    /**
     * 根据computable 计算状态是否退出, Computable.compute()返回 true 退出
     * 适合执行时间比“间隔”长的任务
     * @param command
     *              执行体
     * @param initialDelay
     *              初始延迟执行时间
     * @param period
     *              开始时间的间隔为period,即“固定间隔”执行
     * @param computable
     *              计算属性, 是一个随时间可变属性
     * @param unit
     */
    public static void scheduleWithFixedDelayByCompute(Runnable command, long initialDelay, long period, Computable computable, TimeUnit unit) {
        ScheduledThreadPoolExecutor poolExecutor = ScheduledThreadPoolUtil.getInstance();
        ScheduledFuture<?> scheduledFuture = poolExecutor.scheduleAtFixedRate(command, initialDelay, period, unit);
        for(;;) {
            try {
                TimeUnit.MILLISECONDS.sleep(REJECT_FUTURE_INTERVAL_TIME);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (computable.compute()) {
                poolExecutor.remove((Runnable) scheduledFuture);
                break;
            }
        }
    }
 
    /**
     * 延迟执行一遍
     * @param command
     * @param delay
     * @param unit
     * @return
     */
    public static ScheduledFuture<?> schedule(Runnable command,
                                long delay,
                                TimeUnit unit) {
        return ScheduledThreadPoolUtil.getInstance().schedule(command, delay, unit);
    }
 
    /**
     * 延迟执行一遍
     * @param callable
     * @param delay
     * @param unit
     * @param <V>
     * @return
     */
    public static <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                long delay,
                                TimeUnit unit) {
        return ScheduledThreadPoolUtil.getInstance().schedule(callable, delay, unit);
    }
 
    public static void shutdown() {
        ScheduledThreadPoolUtil.getInstance().shutdown();
    }
}

测试

public static void main(String[] args) {
        //时间退出
        ScheduledThreadPoolUtil.scheduleAtFixedRateByTime(() -> System.out.println(System.currentTimeMillis()),0, 1, 60, TimeUnit.SECONDS);
        //次数退出
        ScheduledThreadPoolUtil.scheduleDelayByNumber(() -> System.out.println(System.currentTimeMillis()), 0, 1, 20, TimeUnit.SECONDS);
        //计算属性退出
        ScheduledThreadPoolUtil.scheduleAtFixedRateByCompute(() -> System.out.println(System.currentTimeMillis()),
                0,
                1,
                () -> {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    int counter = new Random().nextInt(10);
                    if (counter == 2) {
                        System.out.println("back");
                        return true;
                    }
                    return false;
                },
                TimeUnit.SECONDS);
    }

总结

以上便是关于线程池介绍的全部内容,希望能对大家有一定的帮助吧。如果有什么不是很明白的,也可以留言,作者看到了会回复

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值