京东一面:Java 提供哪几种线程池,什么场景用

开发 前端
在某些应用程序中,可能会产生临时文件或日志记录。为了保持系统的整洁和性能,需要定期清理这些临时文件或日志。可以使用 newScheduledThreadPool 来安排清理任务,例如每小时或每天清理一次。

前言

大家好,我是田螺。

我们来看一道京东一面面试题:Java 提供哪几种线程池,什么场景使用?

  • newFixedThreadPool
  • newCachedThreadPool
  • newSingleThreadExecutor
  • newScheduledThreadPool

1. newFixedThreadPool

newFixedThreadPool的构造函数:

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

1.1 线程池特点:

  • 核心线程数和最大线程数大小一样
  • 没有所谓的非空闲时间,即keepAliveTime为0
  • 阻塞队列为无界队列LinkedBlockingQueue

1.2 newFixedThreadPool工作机制

图片图片


  • 提交任务
  • 如果线程数少于核心线程,创建核心线程执行任务
  • 如果线程数已经等于核心线程,把任务添加到LinkedBlockingQueue阻塞队列
  • 如果线程执行完任务,去阻塞队列取任务,继续执行。
  • 如果持续无限添加任务,可能会导致OOM,因为它是无界队列。

1.3 无界队列OOM的实例代码

ExecutorService executor = Executors.newFixedThreadPool(10);
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executor.execute(() -> {
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    //do nothing
                }
            });
        }

为了验证OOM,IDE指定JVM参数:-Xmx8m -Xms8m

运行结果:

图片图片

newFixedThreadPool使用了无界的阻塞队列LinkedBlockingQueue,如果线程获取一个任务后,任务的执行时间比较长(比如,上面demo设置了10秒),会导致队列的任务越积越多,导致机器内存使用不停飙升, 最终导致OOM:

图片图片

大家有兴趣可以看看源码哈:

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

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    ...

    /**
     * Creates a {@code LinkedBlockingQueue} with a capacity of
     * {@link Integer#MAX_VALUE}.
     */
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }
...
}

1.4 使用场景

  • 定时任务调度

对于需要定时执行的任务,如每天的报表生成、数据备份或清理任务,FixedThreadPool 可以保持固定数量的线程来按时执行这些任务,确保系统在高峰期也能稳定运行。

  • 一些后台服务中,比如邮件发送、短信通知等

在一些后台服务中,比如邮件发送、短信通知等,使用 FixedThreadPool 可以确保有足够的线程来处理发送请求,而不会因为突发的高并发请求导致系统崩溃。例如,在一个活动结束后,用户会收到活动总结邮件,固定线程池可以有效管理邮件发送任务,确保每封邮件都能及时发送。

  • 适用于处理CPU密集型的任务

CPU密集型任务是指那些主要依赖于CPU计算能力的任务。这类任务通常需要大量的计算资源,且其执行时间与CPU的处理能力密切相关。与之相对的是I/O密集型任务,后者主要受限于输入/输出操作(如磁盘读写、网络请求等)。

2. newCachedThreadPool

newCachedThreadPool 的构造函数。

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

2.1 线程池特点

  • 核心线程数为0
  • 最大线程数为Integer.MAX_VALUE
  • 阻塞队列是SynchronousQueue
  • 非核心线程空闲存活时间为60秒

当提交任务的速度大于处理任务的速度时,每次提交一个任务,就必然会创建一个线程。极端情况下会创建过多的线程,耗尽 CPU 和内存资源。由于空闲 60 秒的线程会被终止,长时间保持空闲的 CachedThreadPool 不会占用任何资源。

2.2 newCachedThreadPool工作机制

图片图片

  • 提交任务
  • 因为没有核心线程,所以任务直接加到SynchronousQueue队列。
  • 判断是否有空闲线程,如果有,就去取出任务执行。
  • 如果没有空闲线程,就新建一个线程执行。
  • 执行完任务的线程,还可以存活60秒,如果在这期间,接到任务,可以继续活下去;否则,被销毁。

2.3 无界队列OOM的实例代码

newCachedThreadPool 使用不当,也是会导致OOM的,比如以下这个demo:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPoolOOMExample {
    public static void main(String[] args) {
        // 创建一个无界线程池
        ExecutorService executorService = Executors.newCachedThreadPool();

        try {
            // 不断提交任务,模拟内存消耗
            while (true) {
                executorService.submit(() -> {
                    // 模拟一个长时间运行的任务
                    try {
                        // 创建一个大的对象来消耗内存
                        int[] largeArray = new int[1000000]; // 1,000,000 integers
                        // 模拟一些计算
                        for (int i = 0; i < largeArray.length; i++) {
                            largeArray[i] = i;
                        }
                        // 让线程稍微休眠一下
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

2.4 使用场景

使用用于用于并发执行大量短期的小任务。比如一些网络爬虫、Web服务器处理请求。

3. newSingleThreadExecutor

newSingleThreadExecutor的构造函数:

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

3.1  线程池特点

  • 核心线程数为1
  • 最大线程数也为1
  • 阻塞队列是LinkedBlockingQueue
  • keepAliveTime为0

3.2 newSingleThreadExecutor的工作机制

图片图片

  • 提交任务
  • 线程池是否有一条线程在,如果没有,新建线程执行任务
  • 如果有,讲任务加到阻塞队列
  • 当前的唯一线程,从队列取任务,执行完一个,再继续取,一个人(一条线程)夜以继日地干活。

3.3 newSingleThreadExecutor的实例代码

newSingleThreadExecutor 使用的也是无界队列。如果任务提交速率过高,可能会导致系统资源耗尽(如内存溢出)。我们来看一个简单使用demo:

ExecutorService executor = Executors.newSingleThreadExecutor();
                for (int i = 0; i < 5; i++) {
                    executor.execute(() -> {
                        System.out.println(Thread.currentThread().getName()+"正在执行");
                    });
        }

运行结果:

图片图片

3.4 使用场景

适用于串行执行任务的场景,一个任务一个任务地执行。比如任务调度

在某些业务场景中,任务之间存在依赖关系,即一个任务的输出是另一个任务的输入。在这种情况下,使用单线程执行器可以确保任务按预期的顺序执行。

4. newScheduledThreadPool

public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

4.1  线程池特点

  • 最大线程数为Integer.MAX_VALUE
  • 阻塞队列是DelayedWorkQueue
  • keepAliveTime为0
  • scheduleAtFixedRate() :按某种速率周期执行
  • scheduleWithFixedDelay():在某个延迟后执行

4.2 工作机制

  • 添加一个任务
  • 线程池中的线程从 DelayQueue 中取任务
  • 线程从 DelayQueue 中获取 time 大于等于当前时间的task
  • 执行完后修改这个 task 的 time 为下次被执行的时间
  • 这个 task 放回DelayQueue队列中

4.3 实例代码

/**
    创建一个给定初始延迟的间隔性的任务,之后的下次执行时间是上一次任务从执行到结束所需要的时间+* 给定的间隔时间
    */
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleWithFixedDelay(()->{
            System.out.println("current Time" + System.currentTimeMillis());
            System.out.println(Thread.currentThread().getName()+"正在执行");
        }, 1, 3, TimeUnit.SECONDS);

运行结果:图片图片

   /**
    创建一个给定初始延迟的间隔性的任务,之后的每次任务执行时间为 初始延迟 + N * delay(间隔) 
    */
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
            scheduledExecutorService.scheduleAtFixedRate(()->{
            System.out.println("current Time" + System.currentTimeMillis());
            System.out.println(Thread.currentThread().getName()+"正在执行");
        }, 1, 3, TimeUnit.SECONDS);;

周期性执行任务的场景,需要限制线程数量的场景。比如定时清理任务:

在某些应用程序中,可能会产生临时文件或日志记录。为了保持系统的整洁和性能,需要定期清理这些临时文件或日志。可以使用 newScheduledThreadPool 来安排清理任务,例如每小时或每天清理一次。

责任编辑:武晓燕 来源: 捡田螺的小男孩
相关推荐

2024-10-17 16:58:43

2021-12-27 03:40:41

Go场景语言

2022-04-29 13:40:55

前端测试后端

2024-11-11 17:27:45

2021-11-03 09:03:09

面试链接http

2024-11-11 16:40:04

2011-09-01 09:39:06

2021-12-20 23:24:40

前端测试开发

2023-08-30 09:00:05

2024-04-07 08:06:37

Spring事件应用程序

2022-06-15 09:02:32

JVM线程openJDK

2024-11-26 08:52:34

SQL优化Kafka

2022-05-11 22:15:51

云计算云平台

2018-07-28 00:20:15

2024-05-27 09:07:27

2024-08-16 22:06:06

2024-05-15 16:41:57

进程IO文件

2024-04-15 10:30:22

MySQL存储引擎

2010-08-17 13:00:19

DB2数据迁移

2020-07-11 09:42:59

python数据挖掘数据分析
点赞
收藏

51CTO技术栈公众号