【线程池】线程池的7种创建方式,详细讲解

news2024/12/24 0:58:15

文章目录

  • 一、什么是线程池?
  • 二、线程池的分类
  • 三、线程池的使用
  • 四、ThreadPoolExecutor详解

一、什么是线程池?

线程池(ThreadPool)是一种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在一个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可。

池化思想在计算机的应用也比较广泛,比如以下这些:

  • 内存池(Memory Pooling):预先申请内存,提升申请内存速度,减少内存碎片。
  • 连接池(Connection Pooling):预先申请数据库连接,提升申请连接的速度,降低系统的开销。
  • 实例池(Object Pooling):循环使用对象,减少资源在初始化和释放时的昂贵损耗。

线程池的优势主要体现在以下 4 点:

  • 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
  • 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

同时阿里巴巴在其《Java开发手册》中也强制规定:线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

  • 说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。
  • 如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

知道了什么是线程池以及为什要用线程池之后,我们再来看怎么用线程池。

二、线程池的分类

线程池的创建方法总共有 7 种,但总体来说可分为 2 类:
在这里插入图片描述

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

那接下来我们来看每种线程池创建的具体使用。

三、线程池的使用

FixedThreadPool
创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。

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

在这里插入图片描述
CachedThreadPool
创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。

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

在这里插入图片描述
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) {
            }
        });
    }
}

在这里插入图片描述
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);
}

在这里插入图片描述
SingleThreadScheduledExecutor
创建一个单线程的可以执行延迟任务的线程池。

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);
}

在这里插入图片描述
newWorkStealingPool
创建一个抢占式执行的线程池(任务执行顺序不确定),注意此方法只有在 JDK 1.8+ 版本中才能使用。

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()) {
    }
}

在这里插入图片描述
ThreadPoolExecutor
最原始的创建线程池的方式,它包含了 7 个参数可供设置。

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。

所以综上情况所述,我们推荐使用 ThreadPoolExecutor 的方式进行线程池的创建,因为这种创建方式更可控,并且更加明确了线程池的运行规则,可以规避一些未知的风险。

ThreadPoolExecutor 参数介绍

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

参数 1:corePoolSize
核心线程数,线程池中始终存活的线程数。

参数 2:maximumPoolSize
最大线程数,线程池中允许的最大线程数,当线程池的任务队列满了之后可以创建的最大线程数。

参数 3:keepAliveTime
最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程。

参数 4:unit:
单位是和参数 3 存活时间配合使用的,合在一起用于设定线程的存活时间 ,参数 keepAliveTime 的时间单位有以下 7 种可选:

TimeUnit.DAYS:天
TimeUnit.HOURS:小时
TimeUnit.MINUTES:分
TimeUnit.SECONDS:秒
TimeUnit.MILLISECONDS:毫秒
TimeUnit.MICROSECONDS:微妙
TimeUnit.NANOSECONDS:纳秒

参数 5:workQueue
一个阻塞队列,用来存储线程池等待执行的任务,均为线程安全,它包含以下 7 种类型:较常用的是 LinkedBlockingQueue 和 Synchronous,线程池的排队策略与 BlockingQueue 有关。

ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

参数 6:threadFactory
线程工厂,主要用来创建线程,默认为正常优先级、非守护线程。

参数 7:handler
拒绝策略,拒绝处理任务时的策略,系统提供了 4 种可选:默认策略为 AbortPolicy。

AbortPolicy:拒绝并抛出异常。
CallerRunsPolicy:使用当前调用的线程来执行此任务。
DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
DiscardPolicy:忽略并抛弃当前任务。

ThreadPoolExecutor执行流程
ThreadPoolExecutor 关键节点的执行流程如下:

  • 当线程数小于核心线程数时,创建线程。
  • 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
  • 当线程数大于等于核心线程数,且任务队列已满:若线程数小于最大线程数,创建线程;若线程数等于最大线程数,抛出异常,拒绝任务。
    在这里插入图片描述

ThreadPoolExecutor自定义拒绝策略

public static void main(String[] args) {
    // 任务的具体方法
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("当前任务被执行,执行时间:" + new Date() +
                               " 执行线程:" + Thread.currentThread().getName());
            try {
                // 等待 1s
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    // 创建线程,线程的任务队列的长度为 1
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
                                                           100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),
                                                           new RejectedExecutionHandler() {
                                                               @Override
                                                               public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                                                                   // 执行自定义拒绝策略的相关操作
                                                                   System.out.println("我是自定义拒绝策略~");
                                                               }
                                                           });
    // 添加并执行 4 个任务
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    threadPool.execute(runnable);
}

在这里插入图片描述

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

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

相关文章

(哈希表 ) 454. 四数相加 II ——【Leetcode每日一题】

❓454. 四数相加 II 难度&#xff1a;中等 给你四个整数数组 nums1、nums2、nums3 和 nums4 &#xff0c;数组长度都是 n &#xff0c;请你计算有多少个元组 (i, j, k, l) 能满足&#xff1a; 0 < i, j, k, l < nnums1[i] nums2[j] nums3[k] nums4[l] 0 示例 1&a…

如何在多个端口上运行 SSH 服务器?

SSH&#xff08;Secure Shell&#xff09;是一种用于安全远程访问和管理服务器的协议。默认情况下&#xff0c;SSH服务器在Linux系统上使用22号端口进行通信。但是&#xff0c;有时我们可能需要在多个端口上运行SSH服务器&#xff0c;以满足特定的需求或增强服务器的安全性。 本…

Hexo+Twikoo+Vercel 个人博客开启评论功能

Twikoo 文档&#xff1a;https://twikoo.js.org/quick-start.html MongoDB 数据库 点击链接 https://www.mongodb.com/cloud/atlas/register 进入 MongoDB 官网使用邮箱进行注册&#xff1a; 注册之后&#xff0c;MongoDB 会向邮箱发送一封验证邮件&#xff1a; 进入邮箱&…

如何在 Linux 中进行网络地址转换 (NAT)?

网络地址转换&#xff08;Network Address Translation&#xff0c;简称NAT&#xff09;是一种在网络中使用的技术&#xff0c;它允许将私有网络中的IP地址映射到公共网络上&#xff0c;从而实现多个设备共享单个公共IP地址。在Linux系统中&#xff0c;我们可以使用一些工具和配…

实体店引流获客系统模式开发详解

随着互联网的日益发展&#xff0c;实体店的处境变得越来越艰难&#xff0c;获客难和销量差成为了实体店最头疼的两大问题。面对这种情况&#xff0c;一味固步自封是不行的&#xff0c;最好还是顺应潮流&#xff0c;结合一款合适的商业模式&#xff0c;来帮助自己快速引流获客和…

䲟鱼优化算法(ROA)(含MATLAB代码)

先做一个声明&#xff1a;文章是由我的个人公众号中的推送直接复制粘贴而来&#xff0c;因此对智能优化算法感兴趣的朋友&#xff0c;可关注我的个人公众号&#xff1a;启发式算法讨论。我会不定期在公众号里分享不同的智能优化算法&#xff0c;经典的&#xff0c;或者是近几年…

接口测试--apipost接口断言详解

在做接口测试的时候&#xff0c;会对接口进行断言&#xff0c;一个完整的接口测试&#xff0c;包括&#xff1a;请求->获取响应正文->断言。 一、apipost如何进行断言 apipost的断言设置实在后执行脚本中进行编写的。apipost本身提供了11中断言&#xff1a; apt.asser…

经济学基本思维方式

经济学定义 人的欲望是无止境的&#xff0c;而资源有限&#xff0c;这是一个矛盾。 经济学是研究个人和社会如何最优利用由自然和前人提供的有限资源的学问。 经济学的范围 Microeconomics 微观经济学 研究各个行业的运作的以及企业和个人的决策行为的经济学分支。Macroeco…

数据门户基础课程

PC端门户使用示例 首先,以管理员身份登录FineBI系统,安装数据门户,安装步骤见官网 新建一个数据门户

7. Linux系统下在桌面设置添加安装软件启动图标快捷方式

1. 说明 在Linux系统下安装了对应的软件之后&#xff0c;一般启动时需要在命令行终端中进行启动&#xff0c;可以在终端里设置快速启动的命令&#xff0c;相关设置可参考博客&#xff1a;Linux系统下在终端设置快速启动已安装软件。 另一种快捷启动方式就是像在Windows桌面系统…

git报错Filename too long

描述 碰见一个小小问题&#xff0c;Git在拉代码的时候提示Filename too long&#xff0c;具体如图 刚开始&#xff0c;一开始我是使用git自带的那个工具有问题了&#xff0c;然后我就下载了一个tortoisegit&#xff0c;结果还是有问题&#xff0c;这可是22G文件&#xff0c;我…

printf打印彩色字符,还能闪烁!

在使用linux终端命令的时候&#xff0c;我们可以看到像more命令&#xff0c;它的显示方式与一般的字符串不同&#xff0c;是用了反显。同样&#xff0c;linux C下printf还有很多其他不常见的格式化输出形式。本文主要为你盘点这些形式。 效果展示: 特殊的输出 演示代码: #inc…

每日一练 | 华为认证真题练习Day47

1、某台路由器输出信息如下&#xff0c;下列说法错误的是&#xff1f;&#xff08;多选&#xff09; A. 本路由器开启了区域认证 B. 本设备出现故障&#xff0c;配置的Router Id和实际生效的Router ID不一致 C. 本设备生效的Router Id为10.0.12.1 D. 本设备生效的Router Id为…

渗透测试工具Burpsuite

学习文档 https://portswigger.net/burp/documentation/desktop/getting-started/download-and-install Burp Suite是一款流行的集成式Web应用程序安全测试工具套件&#xff0c;广泛应用于渗透测试、安全审计、黑盒测试和漏洞研究等Web安全领域。它由PortSwigger公司开发&…

工具-Ditto与X mouse Button,自定义热键复制粘贴Ctrl C /V,解放双手

文章目录 1、使用演示2、使用方法3、更新&#xff0c;鼠标一键实现复制粘贴3.1 使用方法 1、使用演示 不需要同时摁下ctrl c /v 就可以在键盘上实现一键复制粘贴&#xff0c;其实去网上有外接的键盘&#xff0c;大概20元左右&#xff0c;外接键盘只有两个摁键&#xff0c;就是…

一文解决OpenCloudOS 如何基于 eBPF 实现容器级别的TCP 连接监控?

导语&#xff1a;eBPF 技术的出现&#xff0c;使得内核的资源监控更加的便捷、高效&#xff0c;容器化监控也更加适用于云原生的场景。基于 eBPF 实现的可观测性&#xff0c;可以无需修改内核源码或者加载内核模块&#xff0c;安全高效的扩展内核功能&#xff0c;很好的解决了上…

11.1.0 Web 打印新支持 UGC 布局模板

作者&#xff1a;yx 目录 前言 一、在iDesktopX制作布局并输出布局模板 二、将布局模板拷贝到iServer 三、构建Web打印内容 四、结果展示 前言 之前有小伙伴希望 iServer 的 Web 打印布局可以采用 iDesktop/iDesktopX 输出的布局模板&#xff0c;针对该需求11.1.0版本做了…

基于标准库函数的STM32的freertos的移植(二)——freertos工程源码移植

1.新建Project_Stm32f407的文件夹&#xff0c;如下图所示&#xff1a; 图1 新建Project_Stm32f407的文件夹 2.在Project_Stm32f407的目录下新建7个子目录&#xff08;分别新建cmsis、include、source、fwlib、user、mdk和freertos等7个文件夹&#xff09;。 图2 新建7个文件目录…

Unity 一键修改场景中的字体,修改部分字体

强大的Unity编辑器扩展 介绍&#xff1a; 测试编辑器扩展时顺便做了个小工具&#xff0c;挺有意思 选中场景中的物体后就可以一键修改这个物体下所有字体 如果什么都不选&#xff0c;就是修改这个场景中的全部字体 修改完字体后记得保存场景&#xff01; 代码&#xff0…

《微服务实战》 第二十五章 Java多线程安全与锁

前言 本章节介绍Java多线程安全与锁 1、Java多线程安全与锁 1.1、多线程安全问题 当多个线程同时操作同一个数据时&#xff0c;可能会出现数据不一样的情况&#xff0c;这就是线程安全问题。线程安全机制用于保证多个线程访问数据时的一致性. 1.2、线程安全问题三方面 原…