JavaEE之线程池

embedded/2025/1/17 2:01:53/

前面我们了解了多个任务可以通过创建多个线程去处理,达到节约时间的效果,但是每一次的线程创建和销毁也是会消耗计算机资源的,那么我们是否可以将线程进阶一下,让消耗计算机的资源尽可能缩小呢?线程池可以达到此效果,今天我们对 《线程池》 一探究竟!

1. 何为线程池?

线程池(Thread Pool)是一种并发编程的技术,用于在应用程序启动时创建一定数量的线程,并将它们保存在线程池中,以便于任务的执行和线程的重用。当有新任务到来时,线程池会分配一个空闲线程来执行该任务,任务完成后,线程不会被销毁,而是返回线程池中,等待执行下一个任务。

2. 为什么要用线程池?

为什么要用线程池,那就得从线程池的优点来说了
优点:

  1. 重用线程:避免了频繁创建和销毁线程的开销,提高了线程的利用率。
  2. 控制并发度:可以限制并发执行的线程数量,防止系统过载。
  3. 提供线程管理和监控:方便开发人员进行线程的管理和调试。
  4. 提供任务队列:实现任务的缓冲和调度,提升系统响应速度。

3. 为什么使用线程池可以提升效率?

由于线程的频繁创建和销毁会影响计算机效率,所以我们可以减少线程的频繁创建和销毁来提高计算机效率,因为线程池可以实现这种效果,所以可以提升效率,因为线程池做的是少量创建,少量销毁,进而做到了提升效率的功能
线程池最⼤的好处就是减少每次启动、销毁线程的损耗。
从另一种方面来说,计算机分为 内核态(操作系统层面)用户态JVM层面(应用程序层),创建和销毁线程是内核态的操作

4. 线程池怎么用?

Java当中JDK给我们提供了一组针对不同使用场景的线程池实例,如下:

java">//1.用来处理大量短时间工作任务的线程池,如果池中没有可用的线程将创建新的线程,如果线程空闲60秒将回收并移除缓存
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//2.创建一个操作无界队列且固定大小线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//3.创建一个操作无界队列且只有一个工作线程的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
//4.创建一个单线程执行器,可以在给定时间后执行或定期执行
ScheduledExecutorService singleThreadScheduledExecutor=Executors.newSingleThreadScheduledExecutor();
//5.创建一个指定大小的线程池,可以在给定时间后执行或定期执行
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
//6.创建一个指定大少(不传入参数,为当煎机器CPU核心数)的线程池,并行地处理任务
Executors.newWorkStealingPool();

说明:

  • 使用Executors.newFixedThreadPool(3)能创建出固定包含3个线程的线程池.
  • newCachedThreadPool:创建线程数目动态增长的线程池.
  • newFixedThreadPool:创建固定线程数的线程池 ,返回值类型为ExecutorService.
  • newSingleThreadExecutor: 创建只包含单个线程的线程池.
  • newSingleScheduledThreadPool:创建一个单线程执行器,设定延迟时间后执行命令,或者定期执行命令,是进阶版的Timer.
  • newScheduledThreadPool:创建一个指定大小的线程池,设定延迟时间后执行命令,或者定期执行命令.是进阶版的Timer.
  • 无界:任务队列的容量 没有限制称为无界队列
  • Executors本质上是ThreadPoolExecutor类的封装.
    在这里插入图片描述在这里插入图片描述

5. 自我实现一个线程池

思路:

  1. 首先要描述任务

通过Runnable描述任务

  1. 通过一个阻塞队列管理任务
java">//阻塞队列组织任务,最多处理100个任务
public static BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(100);
  1. 提供一个方法向线程池提交任务
java">//添加任务到阻塞队列当中public void submit(Runnable runnable) throws InterruptedException {if(runnable==null){throw new IllegalAccessError("任务为空");}queue.put(runnable);}
  1. 创建多个线程,不停的扫描队列中的任务
java">//threadNum为初始化时,给定的线程池中含有线程的个数
public MyThreadPool(int threadNum) {if(threadNum<=0){throw new RuntimeException("线程数量必须大于0");}for (int i = 0; i < threadNum; i++) {Thread thread=new Thread(()->{//不停的扫描线程while (true) {try {//从线程中取出任务Runnable runnable = queue.take();//执行任务runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});//启动线程thread.start();}}
  1. 测试我们的线程池是否成功
java">public class Test {public static void main(String[] args) throws InterruptedException {//创建线程数量为3的线程池MyThreadPool myThreadPool=new MyThreadPool(3);//向线程池提交10个任务for (int i = 0; i < 10; i++) {int taskId=i+1;myThreadPool.submit(new Runnable() {@Overridepublic void run() {System.out.println("执行任务"+taskId+", "+Thread.currentThread().getName());}});}}
}
  1. 完整的自我线程池如下:
java">import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;public class MyThreadPool {//阻塞队列组织任务,最多处理100个任务public static BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(100);//添加任务到阻塞队列当中public void submit(Runnable runnable) throws InterruptedException {if(runnable==null){throw new IllegalAccessError("任务为空");}queue.put(runnable);}public MyThreadPool(int threadNum) {if(threadNum<=0){throw new RuntimeException("线程数量必须大于0");}for (int i = 0; i < threadNum; i++) {Thread thread=new Thread(()->{//不停的扫描线程while (true) {try {//从线程中取出任务Runnable runnable = queue.take();//执行任务runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});//启动线程thread.start();}}
}

6. 创建系统自带的线程池

在这里插入图片描述

执行流程:

  1. 添加任务,核心线程从队列中取任务去执行
  2. 核心线程都在工作时,再添加的任务会进入到阻塞队列
  3. 阻塞队列满了后,会创建临时线程
  4. 阻塞队列满了并且临时线程达到最大,执行拒绝策略
    一次性创建最大的线程数(根据机器的配置,比如CPU核数)

图解:

在这里插入图片描述

说明:

  • corePoolSize: 核心线程的数量.(类似于正式员⼯,⼀旦录⽤,永不辞退)
  • maximumPoolSize:线程池中最大的线程数=核心线程+临时线程的数目.(类似于临时⼯:⼀段时间不⼲活,就被辞退)
  • keepAliveTime:临时线程存活的时间
  • unit:临时线程存活的时间单位,是秒,分钟,还是其他值
  • workQueue:组织(保存)任务的队列
  • threadFactory: 创建线程的工厂,参与具体的创建线程⼯作.通过不同线程工厂创建出的线程相当于对一些属性进行了不同的初始化设置
  • RejectedExecutionHandler: 拒绝策略,如果任务量超出线程池的负荷了接下来怎么处理
    在这里插入图片描述

接下来我们探讨一下这四种拒绝策略:

1. AbortPolicy(): 超过负荷,直接抛出异常.

java">import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class TestAbortPolicy {public static void main(String[] args) throws InterruptedException {ThreadPoolExecutor reject1 =new ThreadPoolExecutor(3,5,1,TimeUnit.SECONDS,new LinkedBlockingQueue<>(10),new ThreadPoolExecutor.AbortPolicy());for (int i = 0; i < 100; i++) {int taskId = i + 1;reject1.submit(() -> {System.out.println("执行任务:" + taskId + ", " + Thread.currentThread().getName());});}}
}

在这里插入图片描述

举个例子: 现在我在和我的女友玩游戏,老师让我帮他做一项报告,我直接拒绝了,并抛出异常,我不做!!

2. CallerRunsPolicy():返回给调⽤者负责处理多出来的任务.

java">import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class TestCallerRunsPolicy {public static void main(String[] args) {ThreadPoolExecutor reject2=new ThreadPoolExecutor(3,5,1,TimeUnit.SECONDS,new LinkedBlockingQueue<>(10),new ThreadPoolExecutor.CallerRunsPolicy());for (int i = 0; i < 100; i++) {int taskId = i + 1;reject2.submit(()->{System.out.println("执行任务:" + taskId + ", " + Thread.currentThread().getName());});}}
}

在这里插入图片描述

举个例子: 现在我在和我的女友玩游戏,老师让我帮他做一项报告,我直接拒绝了,老师就自己去做了,保证了任务至少完成了。

3. DiscardOldestPolicy():丢弃队列中最⽼的任务.

java">import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class TestDiscardOldestPolicy {public static void main(String[] args) {ThreadPoolExecutor reject2=new ThreadPoolExecutor(3,5,1,TimeUnit.SECONDS,new LinkedBlockingQueue<>(10),new ThreadPoolExecutor.DiscardOldestPolicy());for (int i = 0; i < 100; i++) {int taskId = i + 1;reject2.submit(()->{System.out.println("执行任务:" + taskId + ", " + Thread.currentThread().getName());});}}
}

在这里插入图片描述

举个例子: 现在我在和我的女友玩游戏,老师让我帮他做一项报告,我直接退出游戏(不处理最老的任务),去完成老师交给我的任务。

4. DiscardPolicy():丢弃新来的任务.

java">import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class TestDiscardPolicy {public static void main(String[] args) {ThreadPoolExecutor reject2=new ThreadPoolExecutor(3,5,1,TimeUnit.SECONDS,new LinkedBlockingQueue<>(10),new ThreadPoolExecutor.DiscardPolicy());for (int i = 0; i < 100; i++) {int taskId = i + 1;reject2.submit(()->{System.out.println("执行任务:" + taskId + ", " + Thread.currentThread().getName());});}}
}

在这里插入图片描述

举个例子: 现在我在和我的女友玩游戏,老师让我帮他做一项报告,我直接拒绝了,老师也不做,日后可能忘记了该任务,再也找不回来。

总结: 在实际的开发过程中,我们要根据实际的业务场景选择不同的拒绝策略!!!

以上就是线程池的基本知识了,希望对你有帮助,谢谢!!


http://www.ppmy.cn/embedded/154528.html

相关文章

RabbitMQ-基本使用

1 概述 RabbitMQ中的几个基本概念&#xff1a; (1)信道(channel)&#xff1a;信道是消息的生产者、消费者和服务器之间进行通信的虚拟连接。为什么叫“虚拟连接”呢&#xff1f;因为TCP连接的建立是非常消耗资源的&#xff0c;所以RabbitMQ在TCP连接的基础上构建了虚拟信道。我…

【Linux】从零开始:编写你的第一个Linux进度条小程序

Linux相关知识点可以通过点击以下链接进行学习一起加油&#xff01;初识指令指令进阶权限管理yum包管理与vim编辑器GCC/G编译器make与Makefile自动化构建GDB调试器与Git版本控制工具 文章目录 一、知识铺垫1.1 回车与换行概念1.2 缓冲区 二、实现简单倒计时三、进度条3.1 Verrs…

【 PID 算法 】PID 算法基础

一、简介 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&#xff09;、Differential&#xff08;微分&#xff09;的缩写。也就是说&#xff0c;PID算法是结合这三种环节在一起的。粘一下百度百科中的东西吧。 顾名思义&#xff0c;…

第十章:电子表格软件Excel

文章目录&#xff1a; 一&#xff1a;界面 1.介绍 2.选项卡 2.1 开始 2.2 插入 2.3 布局 2.4 公式 2.5 数据 2.6 审阅 2.7 视图 2.8 开发工具 2.9 图表工具 二&#xff1a;基础 1.工作簿 2.工作表 3.单元格 4.宏 三&#xff1a;数据 1.数据类型 2.自动填充…

理解Spark中运行程序时数据被分区的过程

在Spark中&#xff0c;数据分区是指将数据集分割成多个小的子集&#xff0c;即分区&#xff0c;以便在集群的多个节点上并行处理&#xff0c;从而提高处理效率。以下通过一个具体例子来理解&#xff1a; 例子背景 假设要分析一个包含100万条销售记录的数据集&#xff0c;每条…

linux 端口转发工具rinetd

rinetd是一个轻量级TCP转发工具&#xff0c;简单配置就可以实现端口映射/转发/重定向。 (1) 源码下载 wget https://li.nux.ro/download/nux/misc/el7/x86_64/rinetd-0.62-9.el7.nux.x86_64.rpm(2) 安装rinetd rpm -ivh rinetd-0.62-9.el7.nux.x86_64.rpm (3) 编辑配置文件 …

正则表达式完全指南

# 正则表达式完全指南 正则表达式&#xff08;Regular Expression&#xff0c;简称 regex 或 regexp&#xff09;是一种强大的文本匹配和处理工具。它使用特定的语法规则来描述字符串的匹配模式&#xff0c;广泛应用于文本搜索、替换和数据验证等场景。 ## 1. 基础语法 ### 1.1…

Docker挂载点

在Docker中&#xff0c;挂载点用于将宿主机上的目录映射到容器内的目录&#xff0c;这样可以实现数据的持久化存储和容器之间的数据共享。通过挂载点&#xff0c;容器可以访问宿主机上的文件系统&#xff0c;同时也能够持久化数据&#xff0c;即使容器本身被销毁&#xff0c;数…