0

0

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

P粉602998670

P粉602998670

发布时间: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
实例。它的构造函数是这样的:

视野自助系统小型企业版2.0 Build 20050310
视野自助系统小型企业版2.0 Build 20050310

自定义设置的程度更高可以满足大部分中小型企业的建站需求,同时修正了上一版中发现的BUG,优化了核心的代码占用的服务器资源更少,执行速度比上一版更快 主要的特色功能如下: 1)特色的菜单设置功能,菜单设置分为顶部菜单和底部菜单,每一项都可以进行更名、选择是否隐 藏,排序等。 2)增加企业基本信息设置功能,输入的企业信息可以在网页底部的醒目位置看到。 3)增加了在线编辑功能,输入产品信息,企业介绍等栏

下载
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工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

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

1010

2023.08.02

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

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

611

2024.08.29

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

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

334

2025.08.29

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

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

235

2025.08.29

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

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

443

2023.07.18

堆和栈区别
堆和栈区别

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

605

2023.08.10

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

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

765

2023.08.10

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

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

385

2023.06.29

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

3

2026.03.11

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4.3万人学习

C# 教程
C# 教程

共94课时 | 11.1万人学习

Java 教程
Java 教程

共578课时 | 80.7万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号