【Java 面试题合集】ThreadPoolExecutor 线程池面试题

文章目录

  • 自定义的线程池的 7 个参数
  • 如何合理设置核心线程数 corePoolSize 的大小
    • 《JAVA 并发编程实战》中的方案
  • java 开发手册中为什么不允许使用 Executors 默认的实现?
  • 一个线程池中的线程异常了,那么线程池会怎么处理这个线程?
  • 线程池被创建后里面有线程吗?有什么方法对线程池进行预热吗?
  • submit 和 execute 两个方法有什么区别?
  • shutdownNow() 和 shutdown() 两个方法有什么区别?
  • 调用了 shutdownNow 或者 shutdown,线程一定会退出么?
  • 为什么线程池要使用阻塞队列?
  • 线程池扩展

博主其他 ThreadPoolExecutor 线程池相关文章传送门:
《ThreadPoolExecutor源码解析》
《JDK8 ThreadPoolExecutor 线程池源码深度解析(附几种线程池的扩展方式)》
《java 线程池 ThreadPoolExecutor 源码扩展 支持先增加非核心线程处理任务后放任务队列

自定义的线程池的 7 个参数

maximumPoolSize 最大线程数
keepAliveTime 存活时间
unit 时间单位
workQueue 存放待执行任务的队列
threadFactory 创建线程的工厂。创建线程或线程池时指定有意义的线程名称,方便出错时回溯。
handler 拒绝策略

项目中,我们会使用简单的线程隔离的方案,每个业务使用一个线程池。

如何合理设置核心线程数 corePoolSize 的大小

这个是要根据具体的业务来决定的,如果是「CPU 密集型任务」:比如像加解密,压缩、计算等一系列需要大量耗费 CPU 资源的任务,大部分场景下都是纯 CPU 计算。尽量使用较小的线程池,一般为 CPU 核心数+1。因为 CPU 密集型任务使得 CPU 使用率很高,若开过多的线程数,会造成 CPU 过度切换。如果是「IO 密集型任务」:比如像 MySQL 数据库、文件的读写、网络通信等任务,这类任务不会特别消耗 CPU 资源,但是 IO 操作比较耗时,会占用比较多时间。可以使用稍大的线程池,一般为 2 * CPU 核心数。IO 密集型任务 CPU 使用率并不高,因此可以让 CPU 在等待 IO 的时候有其他线程去处理别的任务,充分利用 CPU 时间。

《JAVA 并发编程实战》中的方案

CPU 密集型:核心数 + 1(即使当计算(CPU)密集型的线程偶尔由于页缺失故障或者其他原因而暂停时,这个“额外”的线程也能确保 CPU 的时钟周期不会被浪费。)
IO 密集型:
【Java 面试题合集】ThreadPoolExecutor 线程池面试题_第1张图片

java 开发手册中为什么不允许使用 Executors 默认的实现?

FixedThreadPool 和 SingleThreadPool 的 workQueue 长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
CachedThreadPool 的 maximumPoolSize 数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

public static ExecutorService newFixedThreadPool(int nThreads) {  
    return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,  
                                  new LinkedBlockingQueue<Runnable>());  
}

public static ExecutorService newSingleThreadExecutor() {  
    return new FinalizableDelegatedExecutorService  
        (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,  
                                new LinkedBlockingQueue<Runnable>()));  
}

public static ExecutorService newCachedThreadPool() {  
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,  
                                  new SynchronousQueue<Runnable>());  
}

一个线程池中的线程异常了,那么线程池会怎么处理这个线程?

  1. 当执行方式是 execute 时,可以看到堆栈异常的输出。
  2. 当执行方式是 submit 时,堆栈异常没有输出。但是调用 Future.get()方法时,可以捕获到异常。
  3. 不会影响线程池里面其他线程的正常执行。
  4. 线程池会把这个线程移除掉,并创建一个新的线程放到线程池中。

线程池被创建后里面有线程吗?有什么方法对线程池进行预热吗?

线程池被创建后如果没有任务过来,里面是不会有线程的。如果需要预热的话可以调用下面的两个方法:prestartAllCoreThreads() 和 prestartCoreThread()。

    // Starts all core threads.
    public int prestartAllCoreThreads() {
        int n = 0;
        while (addWorker(null, true))
            ++n;
        return n;
    }

    // Starts a core thread.
    public boolean prestartCoreThread() {
        return workerCountOf(ctl.get()) < corePoolSize &&
            addWorker(null, true);
    }

submit 和 execute 两个方法有什么区别?

submit() 和 execute() 都可以往线程池中提交任务,区别是使用 execute() 执行任务无法获取到任务执行的返回值,而使用 submit()方法, 可以使用 Future 来获取任务执行的返回值。

shutdownNow() 和 shutdown() 两个方法有什么区别?

shutdownNow() 和 shutdown() 都是用来终止线程池的,它们的区别是,使用 shutdown() 程序不会报错,也不会立即终止线程,它会等待线程池中的缓存任务执行完之后再退出,执行了 shutdown() 之后就不能给线程池添加新任务了;shutdownNow() 会试图立马停止任务,线程中的任务不会再执行,也无法添加新的任务。

调用了 shutdownNow 或者 shutdown,线程一定会退出么?

这个是不一定的,因为线程池会调用线程的 interrupt() 来中断线程的执行,但是这个方法不会打断正在运行的线程,只对正在阻塞等待的线程生效,一旦线程执行的任务类似于一个死循环,那么任务永远不会执行完,那么线程永远都不会退出。

为什么线程池要使用阻塞队列?

因为线程一旦任务执行完之后,如果想让线程不退出,只能阻塞或者自旋来保证线程不会退出,阻塞会让 cpu 资源,但是自旋不会,所以为了防止线程退出和减少 cpu 的消耗,选择使用阻塞队列来保证线程不会退出。

线程池扩展

Java 线程池实现原理及其在美团业务中的实践

你可能感兴趣的:(并发编程,JUC,源码解析,java)