线程池?面试?看这篇就够了!

本文详细介绍了Java线程池的作用,如降低资源消耗和提高响应速度,并深入解析了JDK中的Executor、ExecutorService、AbstractExecutorService、ThreadPoolExecutor、ScheduledExecutorService和ScheduledThreadPoolExecutor等核心接口和类。通过源码分析,阐述了线程池的实现原理和关键方法,如submit、invokeAll、invokeAny等,以及如何通过Executors工具类简化线程池的创建。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ThreadPool

本文原创地址,我的博客https://jsbintask.cn/2019/03/10/jdk/jdk8-threadpool/(食用效果最佳),转载请注明出处!

前言

在实际工作中,线程是一个我们经常要打交道的角色,它可以帮我们灵活利用资源,提升程序运行效率。但是我们今天不是探讨线程!我们今天来聊聊另一个与线程息息相关的角色:线程池.本篇文章的目的就是全方位的解析线程池的作用,以及jdk中的接口,实现以及原理,另外对于某些重要概念,将从源码的角度探讨。
tip:本文较长,建议先码后看。

线程池介绍

首先我们看一段创建线程并且运行的常用代码:

for (int i = 0; i < 100; i++) {
   
    new Thread(() -> {
   
        System.out.println("run thread->" + Thread.currentThread().getName());
        //to do something, send email, message, io operator, network...
    }).start();
}

上面的代码很容易理解,我们为了异步,或者效率考虑,将某些耗时操作放入一个新线程去运行,但是这样的代码却存在这样的问题:

  1. 创建销毁线程资源消耗; 我们使用线程的目的本是出于效率考虑,可以为了创建这些线程却消耗了额外的时间资源,对于线程的销毁同样需要系统资源。
  2. cpu资源有限,上述代码创建线程过多,造成有的任务不能即时完成,响应时间过长。
  3. 线程无法管理,无节制地创建线程对于有限的资源来说似乎成了“得不偿失”的一种作用。
    手动创建执行线程存在以上问题,而线程池就是用来解决这些问题的。怎么解决呢?我们可以先粗略的定义一下线程池:

线程池是一组已经创建好的,一直在等待任务执行的线程的集合。

因为线程池中线程是已经创建好的,所以对于任务的执行不会消耗掉额外的资源,线程池中线程个数由我们自定义添加,可相对于资源,资源任务做出调整,对于某些任务,如果线程池尚未执行,可手动取消,线程任务变得能够管理!
所以,线程池的作用如下:

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性。

jdk线程池详解

上面我们已经知道了线程池的作用,而对于这样一个好用,重要的工具,jdk当然已经为我们提供了实现,这也是本篇文章的重点。
在jdk中,关于线程池的接口,类都定义在juc(java.util.concurrent)包中,这是jdk专门为我们提供用于并发编程的包,当然,本篇文章我们只介绍与线程池有关的接口和类,首先我们看下重点要学习的接口和类:
ThreadPool
如图所示,我们将一一讲解这6个类的作用并且分析。

Executor

首先我们需要了解就是Executor接口,它有一个方法,定义如下:
ThreadPool
Executor自jdk1.5引入,这个接口只有一个方法execute声明,它的作用以及定义如下:接收一个任务(Runnable)并且执行。注意:同步执行还是异步执行均可
由它的定义我们就知道,它是一个线程池最基本的作用。但是在实际使用中,我们常常使用的是另外一个功能更多的子类ExecutorService

ExecutorService

ThreadPool
这个接口继承自Executor,它的方法定义就丰富多了,可以关闭,提交Future任务,批量提交任务,获取执行结果等,我们一一讲解下每个方法作用声明:

  1. void shutdown(): “优雅地”关闭线程池,为什么是“优雅地”呢?因为这个线程池在关闭前会先等待线程池中已经有的任务执行完成,一般会配合方法awaitTermination一起使用,调用该方法后,线程池中不能再加入新的任务。
  2. List<Runnable> shutdownNow();: “尝试”终止正在执行的线程,返回在正在等待的任务列表,调用这个方法后,会调用正在执行线程的interrupt()方法,所以如果正在执行的线程如果调用了sleep,join,await等方法,会抛出InterruptedException异常。
  3. boolean awaitTermination(long timeout, TimeUnit unit): 该方法是一个阻塞方法,参数分别为时间和时间单位。这个方法一般配合上面两个方法之后调用。如果先调用shutdown方法,所有任务执行完成返回true,超时返回false,如果先调用的是shutdownNow方法,正在执行的任务全部完成true,超时返回false。
  4. boolean isTerminated();: 调用方法1或者2后,如果所有人物全部执行完毕则返回true,也就是说,就算所有任务执行完毕,但是不是先调用1或者2,也会返回false。
  5. <T> Future<T> submit(Callable<T> task);: 提交一个能够返回结果的Callable任务,返回任务结果抽象对象是Future,调用Future.get()方法可以阻塞等待获取执行结果,例如:
    result = exec.submit(aCallable).get();,提交一个任务并且一直阻塞知道该任务执行完成获取到返回结果。
  6. <T> Future<T> submit(Runnable task, T result);: 提交一个Runnable任务,执行成功后调用Future.get()方法返回的是result(这是什么骚操作?)。
  7. Future<?> submit(Runnable task);:和6不同的是调用Future.get()方法返回的是null(这又是什么操作?)。
  8. <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks): 提交一组任务,并且返回每个任务执行结果的抽象对象List<Future<T>>,Future作用同上,值得注意的是:
    当调用其中任一Future.isDone()(判断任务是否完成,正常,异常终止都算)方法时,必须等到所有任务都完成时才返回true,简单说:全部任务完成才算完成
  9. <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit): 同方法8,多了一个时间参数,不同的是:如果超时,Future.isDone()同样返回true。
  10. <T> T invokeAny(Collection<? extends Callable<T>> tasks):这个看名字和上面对比就容易理解了,返回第一个正常完成的任务地执行结果,后面没有完成的任务将被取消。
  11. <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit):同10相比,多了一个超时参数。不同的是:在超时时间内,一个任务都没有完成,将抛出TimeoutException
    到现在,我们已经知道了一个线程池基本的所有方法,知道了每个方法的作用,接下来我们就来看看具体实现,首先我们研究下ExecutorService的具体实现抽象类:AbstractExecutorService

AbstractExecutorService

ThreadPool
AbstractExecutorService是一个抽象类,继承自ExecutorService,它实现了ExecutorService接口的submit, invokeAll, invokeAny方法,主要用于将ExecutorService的公共实现封装,方便子类更加方便使用,接下来我们看看具体实现:

1. submit方法:
public Future<?> submit(Runnable task) {
   
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
   
    return new FutureTask<T>(callable);
}
  • 判空
  • 利用task构建一个Future的子类RunnableFuture,最后返回
  • 执行这个任务(execute方法声明在Executor接口中,所以也是交由子类实现)。
    execute方法交由子类实现了,这里我们主要分析ne
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值