百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

面试突击28:线程池有几种创建方式?推荐使用哪种?

moboyou 2025-06-05 16:49 22 浏览

在 Java 语言中,并发编程都是通过创建线程池来实现的,而线程池的创建方式也有很多种,每种线程池的创建方式都对应了不同的使用场景,总体来说线程池的创建可以分为以下两类:

  • 通过 ThreadPoolExecutor 手动创建线程池。
  • 通过 Executors 执行器自动创建线程池。

而以上两类创建线程池的方式,又有 7 种具体实现方法,这 7 种实现方法分别是:

  1. Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
  2. Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
  3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。
  4. Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。
  5. Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。
  6. Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
  7. ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。

接下来我们分别来看这 7 种线程池的具体使用。

1.FixedThreadPool

创建一个固定大小的线程池,可控制并发线程数。

使用 FixedThreadPool 创建 2 个固定大小的线程池,具体实现代码如下:

public static void fixedThreadPool() {
    // 创建 2 个线程的线程池
    ExecutorService threadPool = Executors.newFixedThreadPool(2);

    // 创建任务
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
        }
    };

    // 线程池执行任务(一次添加 4 个任务)
    // 执行任务的方法有两种:submit 和 execute
    threadPool.submit(runnable);  // 执行方式 1:submit
    threadPool.execute(runnable); // 执行方式 2:execute
    threadPool.execute(runnable);
    threadPool.execute(runnable);
}

以上程序的执行结果如下图所示:

如果觉得以上方法比较繁琐,还用使用以下简单的方式来实现线程池的创建和使用:

public static void fixedThreadPool() {
    // 创建线程池
    ExecutorService threadPool = Executors.newFixedThreadPool(2);
    // 执行任务
    threadPool.execute(() -> {
        System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
    });
}

2.CachedThreadPool

创建一个可缓存的线程池,若线程数超过任务所需,那么多余的线程会被缓存一段时间后才被回收,若线程数不够,则会新建线程。

CachedThreadPool 使用示例如下:

public static void cachedThreadPool() {
    // 创建线程池
    ExecutorService threadPool = Executors.newCachedThreadPool();
    // 执行任务
    for (int i = 0; i < 10; i++) {
        threadPool.execute(() -> {
            System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
            }
        });
    }
}

以上程序的执行结果如下图所示:

从上述结果可以看出,线程池创建了 10 个线程来执行相应的任务。

使用场景

CachedThreadPool 是根据短时间的任务量来决定创建的线程数量的,所以它适合短时间内有突发大量任务的处理场景。

3.SingleThreadExecutor

创建单个线程的线程池,它可以保证先进先出的执行顺序。

SingleThreadExecutor 使用示例如下:

public static void singleThreadExecutor() {
    // 创建线程池
    ExecutorService threadPool = Executors.newSingleThreadExecutor();
    // 执行任务
    for (int i = 0; i < 10; i++) {
        final int index = i;
        threadPool.execute(() -> {
            System.out.println(index + ":任务被执行");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
            }
        });
    }
}

以上程序的执行结果如下图所示:

单个线程的线程池有什么意义?

单个线程的线程池相比于线程来说,它的优点有以下 2 个:

  • 可以复用线程:即使是单个线程池,也可以复用线程。
  • 提供了任务管理功能:单个线程池也拥有任务队列,在任务队列可以存储多个任务,这是线程无法实现的,并且当任务队列满了之后,可以执行拒绝策略,这些都是线程不具备的。

4.ScheduledThreadPool

创建一个可以执行延迟任务的线程池。

使用示例如下:

public static void scheduledThreadPool() {
    // 创建线程池
    ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
    // 添加定时执行任务(1s 后执行)
    System.out.println("添加任务,时间:" + new Date());
    threadPool.schedule(() -> {
        System.out.println("任务被执行,时间:" + new Date());
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
    }, 1, TimeUnit.SECONDS);
}

以上程序的执行结果如下图所示:

从上述结果可以看出,任务在 1 秒之后被执行了,实现了延迟 1s 再执行任务。

5.SingleThreadScheduledExecutor

创建一个单线程的可以执行延迟任务的线程池,此线程池可以看作是 ScheduledThreadPool 的单线程池版本。

它的使用示例如下:

public static void SingleThreadScheduledExecutor() {
    // 创建线程池
    ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
    // 添加定时执行任务(2s 后执行)
    System.out.println("添加任务,时间:" + new Date());
    threadPool.schedule(() -> {
        System.out.println("任务被执行,时间:" + new Date());
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
    }, 2, TimeUnit.SECONDS);
}

以上程序的执行结果如下图所示:

从上述结果可以看出,任务在 2 秒之后被执行了。

6.newWorkStealingPool

创建一个抢占式执行的线程池(任务执行顺序不确定),此方法是 JDK 1.8 版本新增的,因此只有在 JDK 1.8 以上的程序中才能使用。

newWorkStealingPool 使用示例如下:

public static void workStealingPool() {
    // 创建线程池
    ExecutorService threadPool = Executors.newWorkStealingPool();
    // 执行任务
    for (int i = 0; i < 10; i++) {
        final int index = i;
        threadPool.execute(() -> {
            System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName());
        });
    }
    // 确保任务执行完成
    while (!threadPool.isTerminated()) {
    }
}

以上程序的执行结果如下图所示:

从上述结果可以看出,任务的执行顺序是不确定的,因为它是抢占式执行的。

7.ThreadPoolExecutor

ThreadPoolExecutor 是最原始、也是最推荐的手动创建线程池的方式,它在创建时最多提供 7 个参数可供设置。

ThreadPoolExecutor 使用示例如下:

public static void myThreadPoolExecutor() {
    // 创建线程池
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
    // 执行任务
    for (int i = 0; i < 10; i++) {
        final int index = i;
        threadPool.execute(() -> {
            System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
}

以上程序的执行结果如下图所示:

ThreadPoolExecutor 相比于其他创建线程池的优势在于,它可以通过参数来控制最大任务数和拒绝策略,让线程池的执行更加透明和可控,所以在阿里巴巴《Java开发手册》是这样规定的:

【强制要求】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。


说明:Executors 返回的线程池对象的弊端如下:

1) FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2)CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

总结

线程池的创建方式总共有以下 7 种:

  1. Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
  2. Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
  3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。
  4. Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。
  5. Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。
  6. Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
  7. ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。

线程池的创建推荐使用最后一种 ThreadPoolExecutor 的方式来创建,因为使用它可以明确线程池的运行规则,规避资源耗尽的风险

相关推荐

python新手学习常见数据类型——数字

Python支持三种不同的数值类型:整型(int)、浮点型(float)、复数(complex)创建数字:a=1b=2.7c=8+4j删除数字:a=1b=2.7c=8+4...

只用一个套路公式,给 Excel 中一列人员设置随机出场顺序

很多同学会觉得Excel单个案例讲解有些碎片化,初学者未必能完全理解和掌握。不少同学都希望有一套完整的图文教学,从最基础的概念开始,一步步由简入繁、从入门到精通,系统化地讲解Excel的各个知...

Excel神技 TIME函数:3秒搞定时间拼接!职场人必学的效率秘籍

你是否经常需要在Excel中手动输入时间,或者从不同单元格拼接时、分、秒?今天我要揭秘一个超实用的Excel函数——TIME函数,它能让你3秒内生成标准时间格式,彻底告别繁琐操作!一、TIME函数基础...

销售算错数被批?97 Excel 数字函数救场,3 步搞定复杂计算

销售部小张被老板当着全部门骂。上季度销售额汇总,他把38652.78算成36852.78,差了1800块。财务对账时发现,整个部门的提成表都得重算。"连个数都算不对,还做什么销售?&...

如何使用Minitab 1分钟生成所需要的SPC数据

打开Minitab,“计算”-“随机数据”-“正太”,因为不好截图,使用的是拍照记录的方式.再要生产的行数中,填写125,可以按照要求,有些客户要求的是100个数据,就可以填写100...

验证码,除了 12306,我还没有服过谁

为了防止暴力注册或爬虫爬取等机器请求,需要验证操作者是人还是机器,便有了验证码这个设计。本文作者主要介绍了如何使用Axure来设计一个动态的图形验证码,一起来学习一下吧。在软件设计中,为了防止暴力...

零基础也能学会的9个Excel函数,小白进阶必备

今天给大家分享一些常用的函数公式,可以有效地解决Excel中办公所需,0基础也可以轻松学会。建议收藏,在需要的时候可以直接套用函数。1、计算排名根据总和,计算学生成绩排名。函数公式=RANK(E2,$...

[office] excel表格数值如何设置_excel表格怎样设置数值

excel表格数值如何设置  因为电子表格应用程序是用来处理数值数据的,所以数值格式可能是工作表中最关键的部分,格式化数值数据的方式由用户决定,但在每个工作簿的工作表之间应使用一致的处理数字的方法。...

Excel最常用的5个函数!会用最后一个才是高手

是不是在处理Excel数据时,面对繁琐的操作烦恼不已?手动操作不仅耗时费力,还容易出错。别担心,表姐这就为你揭秘Excel中几个超实用的函数,让数据处理变得轻松高效!表姐整理了552页《Office从...

新手必会的53个Excel函数_惊呆小伙伴的全套excel函数技能

(新手入门+进阶+新函数)一、新手入门级(24个)1、Sum函数:求和=Sum(区域)2、Average函数:求平均值=Average(区域)3、Count函数:数字个数=Count(区域)4、Cou...

打工人私藏的4个Excel函数秘籍,效率提升3.7%

小伙伴们好啊,今天咱们分享几个常用函数公式的典型应用。合并内容如下图,希望将B列的姓名,按照不同部门合并到一个单元格里。=TEXTJOIN(",",1,IF(A$2:A$15=D2,B...

Excel偷偷更新的8个函数!原来高手都在用这些隐藏技能

领导突然要销售数据,你手忙脚乱筛选到眼花...同事3分钟搞定的报表,你折腾半小时还在填充公式...明明用了VLOOKUP,却总显示#N/A错误...别慌!今天教你的8个动态数组函数,就像给Excel装...

Excel表格随机函数怎么用?讲解三种随机函数在不同场景的应用

excel随机函数,其特点是能够生成一组随机数字,根据不同需求,还能批量生成小数位和整数,及指定行数和列数,或指定区间范围内的数字。这里根据需求,作者设置了三个问题,第1个是随机生成0至1之间的数字...

单纯随机抽样该如何进行?_单纯随机抽样的适用范围及注意事项

在数据分析中,抽样是指从全部数据中选择部分数据进行分析,以发掘更大规模数据集中的有用信息。在收集数据过程中,绝大多数情况下,并不采取普查的方式获取总体中所有样本的数据信息,而是以各类抽样方法抽取其中若...

随机函数在Excel中的应用_随机函数在excel中的应用实例

【分享成果,随喜正能量】职场,如果你没有价值,那么你随时可能被取代;如果你的价值不如别人,那么社会也不会惯你,你将被无情地淘汰掉。不管什么时候,你一定要学会构建自己的价值。每个人都应该思考这个问题:我...