线程池是如何执行的?任务太多会怎么?

这篇具有很好参考价值的文章主要介绍了线程池是如何执行的?任务太多会怎么?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Java 面试不可能不问线程池,无论是大厂还是小厂。这不,前几天面试阿里时也被问到了这个问题,虽不难,但这里也系统复盘一下。

要搞懂线程池的执行流程,最好的方式是去看它的源码,它的源码如下:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 当前工作的线程数小于核心线程数
    if (workerCountOf(c) < corePoolSize) {
        // 创建新的线程执行此任务
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 检查线程池是否处于运行状态,如果是则把任务添加到队列
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 再次检线程池是否处于运行状态,防止在第一次校验通过后线程池关闭
        // 如果是非运行状态,则将刚加入队列的任务移除
        if (! isRunning(recheck) && remove(command))
            reject(command);
        // 如果线程池的线程数为 0 时(当 corePoolSize 设置为 0 时会发生)
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false); // 新建线程执行任务
    }
    // 核心线程都在忙且队列都已爆满,尝试新启动一个线程执行失败
    else if (!addWorker(command, false)) 
        // 执行拒绝策略
        reject(command);
}

从上述源码我们可以看出,当任务来了之后,线程池的执行流程是:先判断当前线程数是否大于核心线程数?如果结果为 false,则新建线程并执行任务;如果结果为 true,则判断任务队列是否已满?如果结果为 false,则把任务添加到任务队列中等待线程执行,否则则判断当前线程数量是否超过最大线程数?如果结果为 false,则新建线程执行此任务,否则将执行线程池的拒绝策略,如下图所示:
image.png

线程池拒绝策略

当任务过多且线程池的任务队列已满时,此时就会执行线程池的拒绝策略,线程池的拒绝策略默认有以下 4 种:

  1. AbortPolicy:中止策略,线程池会抛出异常并中止执行此任务;
  2. CallerRunsPolicy:把任务交给添加此任务的(main)线程来执行;
  3. DiscardPolicy:忽略此任务,忽略最新的一个任务;
  4. DiscardOldestPolicy:忽略最早的任务,最先加入队列的任务。

默认的拒绝策略为 AbortPolicy 中止策略。

DiscardPolicy拒绝策略

接下来我们以 DiscardPolicy 忽略此任务,忽略最新的一个任务为例,演示一下拒绝策略的具体使用,实现代码如下:

public static void main(String[] args) {
    // 任务的具体方法
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("当前任务被执行,执行时间:" + new Date() +
                               " 执行线程:" + Thread.currentThread().getName());
            try {
                // 等待 1s
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    // 创建线程,线程的任务队列的长度为 1
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
                                                           100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),
                                                           new ThreadPoolExecutor.DiscardPolicy());
    // 添加并执行 4 个任务
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    // 线程池执行完任务,关闭线程池
    threadPool.shutdown();
}

以上程序的执行结果如下:
image.png
从上述执行结果可以看出,给线程池添加了 4 个任务,而线程池只执行了 2 个任务就结束了,其他两个任务执行了拒绝策略 DiscardPolicy 被忽略了,这就是拒绝策略的作用。

AbortPolicy拒绝策略

为了和 DiscardPolicy 拒绝策略对比,我们来演示一下 JDK 默认的拒绝策略 AbortPolicy 中止策略,线程池会抛出异常并中止执行此任务,示例代码如下:

public static void main(String[] args) {
    // 任务的具体方法
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("当前任务被执行,执行时间:" + new Date() +
                               " 执行线程:" + Thread.currentThread().getName());
            try {
                // 等待 1s
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    // 创建线程,线程的任务队列的长度为 1
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
                                                           100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),
                                                           new ThreadPoolExecutor.AbortPolicy()); // 显式指定拒绝策略,也可以忽略此设置,它为默认拒绝策略
    // 添加并执行 4 个任务
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    // 线程池执行完任务,关闭线程池
    threadPool.shutdown();
}

以上程序的执行结果如下:
image.png
从结果可以看出,给线程池添加了 4 个任务,线程池正常执行了 2 个任务,其他两个任务执行了中止策略,并抛出了拒绝执行的异常 RejectedExecutionException。

自定义拒绝策略

当然除了 JDK 提供的四种拒绝策略之外,我们还可以实现通过 new RejectedExecutionHandler,并重写 rejectedExecution 方法来实现自定义拒绝策略,实现代码如下:

public static void main(String[] args) {
    // 任务的具体方法
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("当前任务被执行,执行时间:" + new Date() +
                               " 执行线程:" + Thread.currentThread().getName());
            try {
                // 等待 1s
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    // 创建线程,线程的任务队列的长度为 1
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
                                                           100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),
                                                           new RejectedExecutionHandler() {
                                                               @Override
                                                               public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                                                                   // 执行自定义拒绝策略的相关操作
                                                                   System.out.println("我是自定义拒绝策略~");
                                                               }
                                                           });
    // 添加并执行 4 个任务
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    threadPool.execute(runnable);
}

以上程序的执行结果如下:
image.png

小结

线程池的执行流程有 3 个重要的判断点(判断顺序依次往后):判断当前线程数和核心线程数、判断当前任务队列是否已满、判断当前线程数是否已达到最大线程数。如果经过以上 3 个判断,得到的结果都会 true,则会执行线程池的拒绝策略。JDK 提供了 4 种拒绝策略,我们还可以通过 new RejectedExecutionHandler 并重写 rejectedExecution 方法来实现自定义拒绝策略。

本文已收录至《Java面试突击》,专注 Java 面试 100 年,查看更多:www.javacn.site文章来源地址https://www.toymoban.com/news/detail-462939.html

到了这里,关于线程池是如何执行的?任务太多会怎么?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • for循环内线程池并发执行任务,等到子线程全部处理完任务,主线程在执行java的实现方式

    for循环内线程池并发执行任务,等到子线程全部处理完任务,主线程在执行 方式一 使用 CountDownLatch 在 Java 中,您可以使用 CountDownLatch 来实现主线程等待子线程执行完成的功能。CountDownLatch 是一个同步工具类,它允许一个或多个线程等待其他线程完成操作后再继续执行。 具

    2024年02月11日
    浏览(31)
  • 循环交替执行多线程任务-Java

    2024年02月16日
    浏览(27)
  • 使用多线程执行任务,并获取返回结果,附异步实现

    这里创建了一个包含三个线程的固定线程池 线程池的介绍 根据主机情况实现自定义线程池: 也可以通过继承 ThreadPoolExecutor 类来实现一个自定义线程池工具类。ThreadPoolExecutor 是 Java 标准库中提供的一个线程池实现,通过继承它,我们可以实现自定义的线程池。 下面是一个继

    2024年02月16日
    浏览(29)
  • SpringBoot数据源——为什么平时默认线程池是Hikari

    本文主要对DataSourceAutoConfiguration类进行讲解,然后对这个类的注解做一个解释,所以直接打开这个类,开始看…… ps:我用的版本是2.3.0.RELEASE 目录 DataSourceAutoConfiguration类注解 @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) @ConditionalOnMis

    2024年02月06日
    浏览(40)
  • 【Android】使用 `reactor.core.scheduler.Schedulers` 提供的线程池让开发者更加灵活地执行任务

    在 Android 开发中,使用 reactor.core.scheduler.Schedulers 提供的线程池可以让开发者更加灵活地执行任务,并且可以根据具体情况选择最合适的线程。 下面列举几种常用的线程池和其适用场景: Schedulers.single() :该调度器会使用单个线程来执行任务,适用于需要按照先后顺序依次执

    2024年02月09日
    浏览(47)
  • 并发编程5:如何执行任务?

    目录 1、线程中执行任务的方式 2、Executor 框架 2.1 - 线程的执行策略 2.2 - 线程池 2.3 - Executor 的生命周期 2.4 - 延任务与周期任务 3、找出可利用的并行性-代码示例 3.1 - 单线程的 I/O 操作 3.2 - 携带任务结果的 Callable 与 Future(重要) 3.3 - 使用 Future 实现页面渲染器 3.5 - Completio

    2024年02月11日
    浏览(22)
  • 如何查看Crontab任务是否执行

    编写了一条crontab如下: 可以通过以下两种方式查看是否任务已经执行。 1、通过/var/log/cron文件查看。 可以看到最后一行,已经执行。 2、通过/var/spool/cron/root的邮件通知进行查看。 可以看到邮件通知(Cron Daemon)已经执行了。 参考文档: 1、 https://blog.csdn.net/liaoyanyunde/article/det

    2024年02月01日
    浏览(58)
  • js中如何顺序执行异步任务

    在js中,任务可分为两种,同步任务和异步任务。 (1) 同步任务 又叫 非耗时任务 ,指的是在主线程排队执行的那些任务 只有前一个任务执行完毕,才能执行后一个任务 (2) 异步任务 又叫 耗时任务 ,异步任务由JavaScript委托给宿主环境进行执行 当异步任务执行完成后,会通知

    2024年02月09日
    浏览(31)
  • xxljob后台集群部署如何避免重复执行任务

    工作中有一场景,后台部署2个节点,同时注册到执行器内,如何避免任务重复执行呢? 其实xxl-job本身已经考虑到这一点,我们只需要在创建任务的时候这样做就可以了: 路由策略:选择 一致性HASH 阻塞处理策略:选择 丢弃后续调度 引自:xxl-job官网

    2024年02月13日
    浏览(34)
  • 如何让Task在非线程池线程中执行?

    Task承载的操作需要被调度才能被执行,由于.NET默认采用基于线程池的调度器,所以Task默认在线程池线程中执行。但是有的操作并不适合使用线程池,比如我们在一个ASP.NET Core应用中承载了一些需要长时间执行的后台操作,由于线程池被用来处理HTTP请求,如果这些后台操作也

    2024年02月06日
    浏览(25)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包