深入理解 Java 线程池:高效并发编程的利器

news2024/9/19 9:56:52

在 Java 并发编程中,线程池是一种强大的工具,它能够有效地管理和复用线程,提高系统的性能和资源利用率。本文将深入探讨 Java 线程池的概念、原理、使用方法以及最佳实践,帮助读者更好地理解和应用线程池技术。

一、引言

在现代软件开发中,并发编程越来越重要。随着系统的复杂性和用户需求的不断增加,如何高效地利用系统资源,提高程序的性能和响应速度,成为了开发者面临的挑战。Java 线程池作为一种高效的并发编程工具,能够帮助我们更好地管理线程,提高系统的吞吐量和响应时间。

二、线程池的概念和作用

(一)概念

线程池是一种用于管理和复用线程的机制。它维护了一组预先创建好的线程,当有任务需要执行时,从线程池中获取一个空闲线程来执行任务,任务执行完成后,线程不会被销毁,而是返回线程池等待下一个任务。

(二)作用

  1. 提高系统性能:通过复用线程,减少了线程创建和销毁的开销,提高了系统的性能。
  2. 控制资源使用:可以限制线程的数量,避免过多的线程竞争系统资源,导致系统性能下降。
  3. 提高响应速度:当有任务需要执行时,能够快速地从线程池中获取一个线程来执行任务,提高了系统的响应速度。
  4. 便于管理:线程池提供了统一的管理接口,可以方便地对线程进行监控、调整和优化。

三、线程池的原理

(一)线程池的组成部分

  1. 线程工厂:用于创建新线程的工厂类。可以通过实现 ThreadFactory 接口来定制线程的创建方式。
  2. 任务队列:用于存储等待执行的任务。当线程池中的线程都在执行任务时,新的任务会被放入任务队列中等待。
  3. 核心线程数:线程池中保持的最小线程数量。即使没有任务需要执行,核心线程也不会被销毁。
  4. 最大线程数:线程池中允许的最大线程数量。当任务队列已满,且当前线程数小于最大线程数时,会创建新的线程来执行任务。
  5. 拒绝策略:当任务队列已满,且线程池中的线程数量达到最大线程数时,对新任务的处理策略。

(二)线程池的工作流程

  1. 当有新任务提交时,首先检查线程池中的线程数量是否小于核心线程数。如果是,则创建一个新线程来执行任务;如果不是,则将任务放入任务队列中等待。
  2. 当任务队列已满,且线程池中的线程数量小于最大线程数时,会创建新的线程来执行任务。
  3. 当任务队列已满,且线程池中的线程数量达到最大线程数时,会根据拒绝策略来处理新任务。
  4. 当线程执行完任务后,会从任务队列中获取下一个任务继续执行。如果任务队列中没有任务,则线程会进入等待状态,直到有新任务提交。

四、Java 中的线程池实现

(一)Executor 框架

Java 中的线程池是通过 Executor 框架来实现的。Executor 框架提供了一套用于管理和执行任务的接口和类,包括 Executor、ExecutorService、ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 等。

  1. Executor:是一个简单的执行器接口,只定义了一个方法 execute (Runnable command),用于执行一个 Runnable 任务。
  2. ExecutorService:是 Executor 的子接口,提供了更多的方法,如提交任务、关闭线程池等。
  3. ThreadPoolExecutor:是 ExecutorService 的实现类,也是最常用的线程池实现类。它可以根据用户的需求自定义线程池的参数,如核心线程数、最大线程数、任务队列等。
  4. ScheduledThreadPoolExecutor:是一种特殊的线程池,它可以在指定的延迟后或定期执行任务。

(二)创建线程池的方法

  1. 使用 Executors 工厂类

    • Executors.newFixedThreadPool (int nThreads):创建一个固定大小的线程池,核心线程数和最大线程数都为指定的参数 nThreads。
    • Executors.newCachedThreadPool ():创建一个可缓存的线程池,核心线程数为 0,最大线程数为 Integer.MAX_VALUE。当有新任务提交时,如果有空闲线程,则使用空闲线程执行任务;如果没有空闲线程,则创建新线程执行任务。
    • Executors.newSingleThreadExecutor ():创建一个单线程的线程池,核心线程数和最大线程数都为 1。所有任务都在同一个线程中按顺序执行。
    • Executors.newScheduledThreadPool (int corePoolSize):创建一个可定时执行任务的线程池,核心线程数为指定的参数 corePoolSize。
  2. 直接使用 ThreadPoolExecutor 构造函数

    • ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue):创建一个线程池,参数分别为核心线程数、最大线程数、线程空闲时间、时间单位和任务队列。
    • ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory):在上面的基础上增加了一个线程工厂参数,可以自定义线程的创建方式。
    • ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler):在上面的基础上增加了一个拒绝策略参数,可以自定义当任务队列已满且线程池中的线程数量达到最大线程数时的处理策略。
    • ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler):包含了所有的参数,可以完全自定义线程池的行为。

五、线程池的参数设置

(一)核心线程数

核心线程数是线程池中保持的最小线程数量。一般来说,核心线程数应该根据系统的负载和任务的类型来设置。如果任务是 CPU 密集型的,核心线程数可以设置为 CPU 核心数加 1;如果任务是 I/O 密集型的,核心线程数可以设置得较大一些,以充分利用系统的资源。

(二)最大线程数

最大线程数是线程池中允许的最大线程数量。一般来说,最大线程数应该根据系统的资源和负载来设置。如果系统资源有限,最大线程数应该设置得较小一些,以避免过多的线程竞争系统资源;如果系统资源充足,最大线程数可以设置得较大一些,以提高系统的吞吐量。

(三)任务队列

任务队列用于存储等待执行的任务。常见的任务队列有以下几种:

  1. LinkedBlockingQueue:基于链表的无界阻塞队列。当任务队列已满时,新的任务会被阻塞,直到有空闲线程来执行任务。
  2. ArrayBlockingQueue:基于数组的有界阻塞队列。当任务队列已满时,新的任务会被阻塞,直到有空闲线程来执行任务。可以通过设置队列的大小来控制任务的积压程度。
  3. SynchronousQueue:同步队列。不存储任务,每个插入操作必须等待另一个线程的移除操作,反之亦然。适用于对任务的处理速度要求非常高的场景。

(四)线程空闲时间

线程空闲时间是指当线程池中的线程数量大于核心线程数时,多余的线程在空闲状态下等待的时间。如果在这段时间内没有新的任务提交,多余的线程会被销毁,以减少系统资源的占用。线程空闲时间应该根据任务的类型和系统的负载来设置。如果任务的执行时间较短,线程空闲时间可以设置得较短一些;如果任务的执行时间较长,线程空闲时间可以设置得较长一些。

(五)拒绝策略

当任务队列已满,且线程池中的线程数量达到最大线程数时,对新任务的处理策略称为拒绝策略。Java 提供了以下几种拒绝策略:

  1. AbortPolicy:直接抛出 RejectedExecutionException 异常,阻止系统正常运行。
  2. CallerRunsPolicy:由调用线程(提交任务的线程)直接执行任务。
  3. DiscardOldestPolicy:丢弃任务队列中最旧的任务,并尝试重新提交新任务。
  4. DiscardPolicy:直接丢弃新任务,不做任何处理。

可以根据实际情况选择合适的拒绝策略。如果任务比较重要,不能被丢弃,可以选择 AbortPolicy 或 CallerRunsPolicy;如果任务可以被丢弃,可以选择 DiscardOldestPolicy 或 DiscardPolicy。

六、线程池的使用方法

(一)提交任务

可以使用 ExecutorService 的 submit () 方法或 execute () 方法来提交任务。submit () 方法会返回一个 Future 对象,可以通过 Future 对象来获取任务的执行结果;execute () 方法没有返回值,无法获取任务的执行结果。

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

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池,包含 5 个线程
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        // 提交任务
        for (int i = 0; i < 10; i++) {
            int taskNumber = i;
            executorService.submit(() -> {
                System.out.println("Task " + taskNumber + " is running.");
            });
        }

        // 关闭线程池
        executorService.shutdown();
    }
}

(二)获取任务执行结果

如果使用 submit () 方法提交任务,可以通过 Future 对象的 get () 方法来获取任务的执行结果。get () 方法会阻塞当前线程,直到任务执行完成并返回结果。

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

public class ThreadPoolExample {
    public static void main(String[] args) throws Exception {
        // 创建一个固定大小的线程池,包含 5 个线程
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        // 提交任务并获取 Future 对象
        Future<Integer> future = executorService.submit(() -> {
            // 模拟耗时任务
            Thread.sleep(2000);
            return 42;
        });

        // 获取任务执行结果
        Integer result = future.get();
        System.out.println("Task result: " + result);

        // 关闭线程池
        executorService.shutdown();
    }
}

(三)关闭线程池

可以使用 ExecutorService 的 shutdown () 方法或 shutdownNow () 方法来关闭线程池。shutdown () 方法会等待所有任务执行完成后再关闭线程池;shutdownNow () 方法会立即停止所有正在执行的任务,并尝试中断所有等待任务的线程,然后关闭线程池。

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

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池,包含 5 个线程
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        // 提交任务
        for (int i = 0; i < 10; i++) {
            int taskNumber = i;
            executorService.submit(() -> {
                System.out.println("Task " + taskNumber + " is running.");
            });
        }

        // 关闭线程池
        executorService.shutdown();
    }
}

七、线程池的最佳实践

(一)合理设置线程池参数

根据系统的负载和任务的类型,合理设置线程池的参数,如核心线程数、最大线程数、任务队列、线程空闲时间和拒绝策略等。可以通过性能测试和监控来调整参数,以达到最佳的性能和资源利用率。

(二)避免任务堆积

如果任务队列中的任务堆积过多,可能会导致系统性能下降。可以通过调整线程池的参数,如增加线程数量、扩大任务队列容量或采用更高效的拒绝策略等,来避免任务堆积。

(三)监控线程池状态

可以通过 JMX(Java Management Extensions)或自定义的监控工具来监控线程池的状态,如线程数量、任务队列长度、任务执行时间等。及时发现和解决问题,保证系统的稳定运行。

(四)避免线程泄漏

在使用线程池时,要注意避免线程泄漏。线程泄漏是指线程在执行任务过程中出现异常或长时间阻塞,导致线程无法被回收,从而占用系统资源。可以通过捕获异常、设置超时时间或使用线程中断机制等方式来避免线程泄漏。

(五)选择合适的任务提交方式

根据任务的特点和需求,选择合适的任务提交方式。如果需要获取任务的执行结果,可以使用 submit () 方法;如果不需要获取任务的执行结果,可以使用 execute () 方法。

八、总结

Java 线程池是一种强大的并发编程工具,它能够有效地管理和复用线程,提高系统的性能和资源利用率。本文介绍了线程池的概念、原理、使用方法以及最佳实践,希望能够帮助读者更好地理解和应用线程池技术。在实际开发中,我们应该根据系统的负载和任务的类型,合理设置线程池的参数,避免任务堆积和线程泄漏,监控线程池的状态,选择合适的任务提交方式,以充分发挥线程池的优势,提高系统的性能和稳定性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2141151.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

进口车电子信息单二维码解密

目录 效果 二维码信息 解密后信息 进口车电子信息单二维码解密 效果 二维码信息 QzcOcj0yNsb9cVZsGoZKBOrBbn4RJ6O0N4q9/R10ANBvPgWt1vO75YmnWHsImhQUluNYC/OUYwWiO2IljHAhPmSAm3BieWZpXwi1IGWzLKAkRGkTUpqhT2pwEhkbMKcFsfsBfxh9MT1KRy2YaDvLKwLvOVHp7ZJUh4DdDof6GBGfsvam…

Json和Http专栏

json 理论 什么是JSON? 规则 被大括号包括的是JSON对象,被中括号包括的是JSON数组. JSON数组JSON对象 实验 构建JSON 用代码实现如下json内容: //构建JSON void WirteJson() {QJsonObject rootObject;//1.插入name字段rootObject.insert("name","china&quo…

OpenAI o1:隐含在训练与推理间的动态泛化与流形分布

随着OpenAI o1发布&#xff0c;进一步激发了产业与学术各界对AGI的期待以及new scaling law下的探索热情&#xff0c;也看到来自社区和专业机构对o1的阐释&#xff0c;但总感觉还差点什么&#xff0c;因此决定以自己的角度分篇幅梳理下&#xff0c;并分享给大伙&#xff1a; O…

使用mlp算法对Digits数据集进行分类

程序功能 这个程序使用多层感知机&#xff08;MLP&#xff09;对 Digits 数据集进行分类。程序将数据集分为训练集和测试集&#xff0c;创建并训练一个具有两个隐藏层的 MLP 模型。训练完成后&#xff0c;模型对测试数据进行预测&#xff0c;并通过准确率、分类报告和混淆矩阵…

vmvare如何给centos7 设置静态IP地址

本章教程,主要介绍如何在vmvare中如何给虚拟机中设置静态IP地址。本章教程中使用的linux发行版是centos7。 目前没有静态IP地址,并且不能联网,此时我们需要给它配置一个静态IP,并且可以实现联网功能。 一、前置步骤 1、网络设置 2、添加网络 添加一个虚拟机网络,选择VMne…

C++笔记---stack和queue

1. stack的介绍及重要接口 stack---栈&#xff0c;是一种“先进后出&#xff0c;后进先出”的数据结构。 此处的stack是STL库中定义的一个类模板&#xff0c;用于实例化出存储各种类型数据的栈。 bool empty() const;判断栈是否为空(空true/非空false)size_t size() const;返…

Kafka日志索引详解与常见问题分析

目录 一、Kafka的Log日志梳理 1、Topic下的消息是如何存储的&#xff1f; 1. log文件追加记录所有消息 2. index和timeindex加速读取log消息日志 2、文件清理机制 1. 如何判断哪些日志文件过期了 2. 过期的日志文件如何处理 3、Kafka的文件高效读写机制 1. Kafka的文件…

刷题日记【160. 相交链表】

160. 相交链表 这虽然是道简单题&#xff0c;但是最简单的方法&#xff08;Set数组存一边然后另一边遍历判断当前结点是否存在于另一边&#xff09;性能很一般&#xff0c;可以思考用双指针来优化写法&#xff08;可以将空间复杂度降至 O(1)&#xff09; 捋思路时&#xff0c;…

Maya怎么把黑色的面反转为白色面

1、选中需要调整的面。 2、点击菜单栏中的“网格显示”&#xff0c;再点击点击“反转(Reverse)”。 3、反转后&#xff0c;原本黑色的面将会变成正常的面&#xff0c;法线方向也会相应改变。 按住ctrlshift鼠标中键 拖动快捷图标至工具栏

NullPointerException 是什么, 如何修复?

下面是chatGPT 01的回复&#xff1a; **NullPointerException**&#xff08;空指针异常&#xff09;是在 Java 等编程语言中出现的运行时错误&#xff0c;当你尝试使用一个尚未初始化的对象引用&#xff08;即&#xff0c;指向 null&#xff09;时就会发生。这个异常表示你的程…

金融教育进乡村:红土散户联盟教你如何分辨好坏资产

2024年8月&#xff0c;【红土散户联盟】再度将关注的目光投向了农村地区&#xff0c;特别是那些经济不发达的地区。作为一个致力于为社会带来积极改变的组织&#xff0c;红土散户联盟再次举办了农村理财讲座&#xff0c;旨在帮助这些地区的居民提高他们的财务管理能力和投资意识…

OJ题-反转链表

给你一个单链表的头节点&#xff0c;请反转链表&#xff0c;并返回新的链表 eg&#xff1a; 1,2,3,4,5--->5,4,3,2,1 //反转链表 struct ListNode* reverseList(struct ListNode* head) {//定义三个变量struct ListNode* n1, * n2, * n3;n1 NULL;n2 head;n3 head->n…

图解Self-Attention和代码实现,大语言模型基础思维导图

文章目录 1 Self-Attention的概念注意优缺点 2 Self-Attention的原理Q,K,V, and Self-Attention计算公式代码实现 Self-Attention的计算细节输入是如何Embedding的&#xff1f;Word EmbeddingsSentence EmbeddingsPre-trained Embeddings SelfAttention是如何计算的计算图 4 Se…

为什么说开放式耳机比入耳式的好?学生党必入的蓝牙耳机推荐

因为开放式耳机相比入耳式耳机更具优势&#xff0c;具体如下&#xff1a; 佩戴舒适度更高&#xff1a; 开放式耳机通常不需要插入耳道&#xff0c;不会对耳道产生压迫&#xff0c;长时间佩戴耳朵不易感到闷热、疼痛或不适&#xff0c;减少了对耳部的物理压迫和摩擦&#xff0…

单硬盘安装Win10和麒麟V10双系统指导建议

随着信创电脑的普及,国产操作系统也逐渐走进了大家的视野,许多人选择了国产操作系统来体验其开源、安全、高效的特性,而Windows系统也是大多数人习惯使用的操作系统。一台电脑上同时安装银河麒麟V10和Windiows10双系统也成为了非常常见的需求。那么,如何在一台电脑上安装银…

阿里员工爆料:阿里前CTO张建峰,也是中专毕业的!之前觉得大专生做到 P9已经很牛了,现在看来,原来中专生也能成为合伙人

《网安面试指南》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484339&idx1&sn356300f169de74e7a778b04bfbbbd0ab&chksmc0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene21#wechat_redirect 《Java代码审…

AI大模型在知识管理平台上的应用:泛微·采知连实现自动采集.精准搜索.智能问答.主动推荐

AI技术的发展&#xff0c;正在推动组织知识管理模式发生变革。知识管理系统通过各种应用实现知识体系落地&#xff0c;当前聚焦于整合生成式AI技术&#xff0c;以提升业务效率。 组织在数字化进程中面临着知识增量增多、知识更新频率变快、知识与业务结合更紧密等挑战&#xff…

三种mybatis表的列名和对象属性名不一致处理方法

目录 三种mybatis表的列名和对象属性名不一致处理方法 1.使用 resultMap 映射 1&#xff09;mapper 2&#xff09;mapper.xml 3&#xff09;测试代码 4&#xff09;测试结果 ​编辑 2.使用别名 1&#xff09;mapper 2&#xff09;mapper.xml 3&#xff09;测试代码 4&#xff0…

浅谈vue2.0与vue3.0的区别(整理十六点)

目录 1. 实现数据响应式的原理不同 2. 生命周期不同 3. vue 2.0 采用了 option 选项式 API&#xff0c;vue 3.0 采用了 composition 组合式 API 4. 新特性编译宏 5. 父子组件间双向数据绑定 v-model 不同 6. v-for 和 v-if 优先级不同 7. 使用的 diff 算法不同 8. 兄弟组…

浅谈住房城乡建设部科技创新平台布局重点方向

最近住房建设部组织开展住房城乡建设部科技创新平台&#xff08;以下简称部科技创新平台&#xff09;申报工作。详细内容见住房城乡建设部科技创新平台开始申报了 (qq.com)。在这里有4大方向共15个课题。内容见下图&#xff1a; 虽然我是做技术的&#xff0c;但是如何体现创新还…