讲师中心 微信公众号

工具推荐

视频效率加速
updream

一键生成高质量推广成片

AniShort
AniShort

AI短剧协同制作平台

Seko
Seko

AI短剧一键生成成片

剧云
剧云

中文短剧剧本制作平台

如何在Java中创建固定大小线程池

小瑶君_1374

小瑶君_1374

发布时间:2025-09-20 22:28:01

|

926人浏览过

|

来源于php中文网

原创

固定大小线程池通过限制并发线程数来控制资源使用,适用于服务器并发处理、批处理、资源受限及计算密集型任务;其核心优势是避免系统过载并提升稳定性。但Executors.newFixedThreadPool()默认使用无界队列,可能导致内存溢出。解决方案是直接使用ThreadPoolExecutor创建线程池,指定有界队列(如ArrayBlockingQueue)和合适的拒绝策略,从而在保证性能的同时规避风险。

如何在java中创建固定大小线程池

在Java里,要创建一个固定大小的线程池,最直接也最常用的方式就是通过

Executors.newFixedThreadPool()
方法。它的核心思想是,无论你提交多少任务,同时执行的线程数量始终保持在一个你设定的上限,这对于控制系统资源、避免过载非常有效。

解决方案

创建固定大小线程池,我们可以这样做:

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

public class FixedThreadPoolDemo {

    public static void main(String[] args) {
        // 创建一个固定大小为3的线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // 提交10个任务
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + " 正在执行任务 " + taskId);
                try {
                    Thread.sleep(1000); // 模拟任务执行时间
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // 重新设置中断状态
                    System.err.println(Thread.currentThread().getName() + " 的任务 " + taskId + " 被中断。");
                }
            });
        }

        // 关闭线程池,等待所有任务完成
        executor.shutdown();
        try {
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                System.err.println("线程池未在指定时间内关闭,尝试强制关闭。");
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
        System.out.println("所有任务提交完毕,线程池已关闭。");
    }
}

这段代码里,我们用

Executors.newFixedThreadPool(3)
创建了一个最多只能同时运行3个线程的线程池。当有任务提交进来时,如果池子里有空闲线程,就直接拿来用;如果没有,任务就会被放到一个等待队列里,直到有线程空闲出来。这在我看来,是管理并发任务、避免系统资源耗尽的一个非常实用的策略。

固定大小线程池的优势与适用场景是什么?

固定大小线程池,顾名思义,它的核心优势在于对并发线程数的严格控制。在我日常开发中,这简直就是处理一些特定场景的“利器”。

立即学习“Java免费学习笔记(深入)”;

首先,资源可控性是它最显著的特点。你想想看,如果你的服务器CPU是四核的,你非要启动几百个线程去跑计算密集型任务,那结果多半是上下文切换的开销把CPU都占满了,实际工作效率反而下降。固定大小线程池就能帮你把并发数限制在一个合理的范围,比如跟CPU核心数相近,这样就能更好地利用CPU资源,避免因为线程过多导致的系统性能急剧下降,甚至OOM(内存溢出)的风险。

其次,它能提供更可预测的性能。由于线程数量恒定,每次任务的执行开销,包括线程创建、销毁的成本,都被摊平了。系统不会因为任务量的波动而频繁地创建或销毁线程,从而减少了不必要的开销,使得整体响应时间和吞吐量在一个相对稳定的区间。

至于适用场景,我个人觉得它在以下几个地方特别出彩:

  • 服务器端处理并发请求:比如一个Web服务器,需要处理大量的客户端连接。我们通常会限制并发处理的请求数量,以保证每个请求都能得到及时响应,而不是因为请求太多导致整个系统卡死。固定大小线程池就能很好地实现这一点。
  • 批处理任务:当你有大量独立的小任务需要处理,但又不想一次性全部启动耗尽资源时,比如图片处理、数据导入导出,用固定大小线程池来分批执行,既能提高效率,又能保证系统稳定。
  • 资源受限的场景:例如数据库连接池、文件IO操作。这些操作往往对并发数有严格限制,固定大小线程池可以确保不会超出这些外部资源的承受能力。
  • 计算密集型任务:如果任务主要是进行CPU计算,那么线程数通常设置为CPU核心数加1或者核心数本身,这样可以最大化CPU的利用率,避免线程过多导致频繁上下文切换。

在我看来,选择固定大小线程池,往往是我们在“性能”和“稳定性”之间找到一个平衡点的明智之举。

newFixedThreadPool底层实现原理及潜在风险有哪些?

Executors.newFixedThreadPool()
用起来确实方便,但它背后的一些设计,值得我们深入了解一下,特别是它的潜在风险。

当我们调用

Executors.newFixedThreadPool(int nThreads)
时,它实际上返回的是一个
ThreadPoolExecutor
实例。它的构造函数是这样的:

Java调试
Java调试

通过 JDWP 实现 Java 远程调试:连接远程 JVM、设置断点、单步执行代码、查看变量值、获取调用栈。适用于需要调试远程 Java 应用、排查运行时状态、追踪代码执行流程的场景。 术语注解 JDWP:Java Debug Wire Protocol,Java 调试有线协议(Java 远程调试底层通信协议) remote JVM:远程 Java 虚拟机 step through code:代码单步调试(步入 / 步过 / 步出调试) inspect variables:变量查看 / 变量检视 stac

下载
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

而

newFixedThreadPool
的默认实现,大致是这样调用的:

return new ThreadPoolExecutor(nThreads, nThreads,
                              0L, TimeUnit.MILLISECONDS,
                              new LinkedBlockingQueue<Runnable>());

这里有几个关键点:

  1. corePoolSize
    和
    maximumPoolSize
    都被设置为
    nThreads
    :这意味着线程池中的线程数量始终是固定的。当有任务提交时,如果池中线程数小于
    nThreads
    ,就会创建新线程来执行任务,直到达到
    nThreads
    。之后,就不会再创建新线程了。
  2. keepAliveTime
    为
    0L
    :由于
    corePoolSize
    和
    maximumPoolSize
    相等,线程池中的线程即使空闲也不会被回收,因为它们都是核心线程。
  3. 使用
    new LinkedBlockingQueue<Runnable>()
    :这才是最大的潜在风险所在。
    LinkedBlockingQueue
    在默认构造时,它的容量是
    Integer.MAX_VALUE
    ,这几乎是一个无界队列。

潜在风险:

这个无界队列意味着什么呢?如果任务提交的速度远超线程池处理任务的速度,那么所有提交的任务都会被堆积到这个

LinkedBlockingQueue
中。队列会不断膨胀,直到耗尽系统内存,最终导致
OutOfMemoryError
。这在我的经验里,是一个非常隐蔽但又极其致命的问题,尤其是在高并发、任务处理耗时较长的场景下,很容易被忽视。

想象一下,你的服务突然涌入大量请求,每个请求都提交一个任务到这个固定大小的线程池。如果任务处理得慢,队列就会像一个无底洞一样,不停地吞噬内存,直到你的应用程序崩溃。而且,由于没有拒绝策略(默认的

AbortPolicy
在队列满时才会生效,但这里队列永远不会满),系统也不会发出任何警告,直到内存耗尽的那一刻。

所以,尽管

newFixedThreadPool
使用起来很方便,但它在背后的无界队列设计,要求我们在实际应用中必须对任务提交的速度和任务处理的耗时有清晰的认识和严格的控制。

如何更灵活地自定义固定大小线程池以避免风险?

鉴于

Executors.newFixedThreadPool()
存在的无界队列风险,我个人更倾向于直接使用
ThreadPoolExecutor
的构造函数来创建线程池。这样我们能完全掌控线程池的各个参数,特别是队列类型和容量,从而规避潜在的内存溢出问题。

这里,我们可以自己指定一个有界队列,并配置合适的拒绝策略。这能让我们在系统资源耗尽之前,对过多的任务进行处理,比如直接拒绝、记录日志或者降级处理。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Executors; // 用于创建ThreadFactory

public class CustomFixedThreadPoolDemo {

    public static void main(String[] args) {
        // 核心线程数和最大线程数都设为3,模拟固定大小
        int corePoolSize = 3;
        int maximumPoolSize = 3;
        // 线程空闲时间,这里设为0,因为是固定大小,线程不会被回收
        long keepAliveTime = 0L;
        TimeUnit unit = TimeUnit.MILLISECONDS;
        // 使用一个有界队列,容量为10
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);

        // 自定义拒绝策略:当队列和线程池都满时,直接抛出RejectedExecutionException
        // 也可以选择CallerRunsPolicy(调用者执行)、DiscardPolicy(直接丢弃)或DiscardOldestPolicy(丢弃最老的)
        RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();

        // 创建自定义的ThreadPoolExecutor
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                unit,
                workQueue,
                Executors.defaultThreadFactory(), // 使用默认的线程工厂
                handler
        );

        // 提交20个任务,观察有界队列和拒绝策略的效果
        for (int i = 0; i < 20; i++) {
            final int taskId = i;
            try {
                executor.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 正在执行任务 " + taskId);
                    try {
                        Thread.sleep(1000); // 模拟任务执行时间
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        System.err.println(Thread.currentThread().getName() + " 的任务 " + taskId + " 被中断。");
                    }
                });
            } catch (Exception e) {
                System.err.println("任务 " + taskId + " 提交失败: " + e.getMessage());
            }
        }

        // 关闭线程池
        executor.shutdown();
        try {
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                System.err.println("线程池未在指定时间内关闭,尝试强制关闭。");
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
        System.out.println("所有任务提交完毕,线程池已关闭。");
    }
}

在这个自定义的例子中,我们:

  1. 明确了
    corePoolSize
    和
    maximumPoolSize
    :都设置为3,确保了固定大小的特性。
  2. 使用了
    ArrayBlockingQueue
    :这是一个有界队列,容量设置为10。这意味着当线程池中的3个线程都在忙碌,并且队列中已经有10个任务在等待时,第14个任务(3个正在执行 + 10个等待 + 1个新提交)再提交进来,就会触发拒绝策略。
  3. 配置了
    RejectedExecutionHandler
    :这里我们选择了
    ThreadPoolExecutor.AbortPolicy()
    ,它会在任务被拒绝时抛出
    RejectedExecutionException
    。在实际生产环境中,你可以根据业务需求选择其他策略,比如
    CallerRunsPolicy
    让提交任务的线程自己去执行任务,或者
    DiscardPolicy
    直接丢弃任务。

通过这种方式,我们不仅创建了一个固定大小的线程池,更重要的是,我们为它加上了一道“安全阀”,避免了因任务量过大导致内存溢出的风险。这种对细节的掌控,在我看来,是构建健壮并发应用的关键。

热门AI工具

更多
WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

Flowith
Flowith Hot

Flowith是一款基于画布和智能体工作流的 AI 协作工作空间。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

PixPix
PixPix Hot

PixPix是一款面向电商视觉生产的AI商品图生成工具。

讯飞智文

一款面向学习和办公场景的AI文档创作工具,可辅助生成PPT与Word文档,提高资料整理和内容制作效率。

花生AI
花生AI Hot

一款AI视频创作工具,主要用于B站推出的AI视频创作工具,适合需要提升相关任务效率的用户。

PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

7319

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3485

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

4588

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

3065

2025.08.29

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

6807

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

2808

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

4758

2023.08.10

数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

3105

2023.06.29

Postman身份认证怎么配置
Postman身份认证怎么配置

本专题从实际API接口调试场景出发,讲解Postman身份认证的完整配置流程,包括常见认证协议介绍、授权参数设置、访问令牌填写、OAuth 2.0授权及认证失败排查,帮助用户正确完成接口授权与访问验证。

40

2026.11.06

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn