Java进阶篇--Executors类创建常见线程池

目录

线程池架构 

newSingleThreadExecutor

newFixedThreadPool

newCachedThreadPool

newScheduledThreadPool

Executors和ThreaPoolExecutor创建线程池的区别

两种提交任务的方法


线程池架构 

线程池是一种线程管理的机制,用于维护和复用线程,以减少线程创建和销毁的开销。

在Java中,线程池的顶级接口是Executor,而真正的线程池接口是ExecutorService。以下是一些重要的类和接口:

  1. ExecutorService:真正的线程池接口,提供了执行、管理和控制任务的方法。

  2. ScheduledExecutorService:继承自ExecutorService,能够解决那些需要任务重复执行的问题,支持定时和周期性任务执行。

  3. ThreadPoolExecutor:ExecutorService的默认实现,提供了丰富的配置选项,如核心线程数、最大线程数、工作队列类型等。

  4. ScheduledThreadPoolExecutor:继承自ThreadPoolExecutor的ScheduledExecutorService接口实现,用于周期性任务调度。

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。

在Java中,可以通过Executors工厂类提供四种常见类型的线程池:

  1. newCachedThreadPool:创建一个可缓存线程池,适用于执行大量短期异步任务的场景,线程数量不受限制,可以灵活回收空闲线程。

  2. newFixedThreadPool:创建一个固定大小的线程池,适用于控制最大并发数的场景,超出的任务会在队列中等待。

  3. newScheduledThreadPool:创建一个定时线程池,用于支持定时及周期性任务执行的场景。

  4. newSingleThreadExecutor:创建一个单线程化的线程池,用于保证所有任务按照指定顺序执行的场景,只会使用唯一的工作线程来执行任务。

通过选择合适的线程池类型,并根据实际需求进行配置,可以有效管理和优化多线程任务的执行。

newSingleThreadExecutor

newSingleThreadExecutor 是 Executors 工厂类提供的一种线程池,它创建的是单线程化的线程池。具体来说,newSingleThreadExecutor 只会使用单个工作线程来执行任务,保证所有任务按照指定顺序(如 FIFO、LIFO、优先级)依次执行。

这种线程池适用于以下场景:

1. 顺序执行:需要按照特定顺序执行任务,避免并发引起的竞争条件或数据一致性问题。

2. 确保任务不被并发执行:有些任务可能不支持并发执行,此时可以使用单线程化的线程池来保证任务的顺序执行。

3. 上下文切换开销较大的任务:对于一些上下文切换开销较大的任务,如果采用并发执行可能带来额外的开销,可以选择单线程化的线程池来避免这种情况。

在单线程化的线程池中,任务会按照提交的顺序依次执行,不会存在并发执行的情况。如果某个任务由于异常结束,后续的任务会继续执行,不会受到影响。

以下是一个简单的 Java 代码示例,演示了如何使用 newSingleThreadExecutor 创建单线程化的线程池,并提交任务执行:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class main {public static void main(String[] args) {// 创建一个单线程化的线程池ExecutorService executor = Executors.newSingleThreadExecutor();// 提交多个任务给线程池执行for (int i = 0; i < 5; i++) {final int taskId = i;executor.submit(() -> {System.out.println("任务 " + taskId + " 正在线程上运行: " + Thread.currentThread().getName());try {Thread.sleep(1000); // 模拟任务执行} catch (InterruptedException e) {Thread.currentThread().interrupt(); // 保持中断状态}});}// 关闭线程池executor.shutdown();}
}

在上面的示例中,我们首先通过 Executors.newSingleThreadExecutor() 方法创建了一个单线程化的线程池。然后,使用 executor.submit() 方法向线程池提交了5个任务,每个任务都会打印当前线程名并模拟执行一段时间。最后,通过 executor.shutdown() 关闭了线程池。 

需要注意的是,虽然该线程池只使用单个工作线程来执行任务,但仍然可以通过 ExecutorService 提供的方法提交多个任务,这些任务会按照提交的顺序被放入队列,并由单个工作线程逐个执行。

总之,SingleThreadExecutor 用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行

newFixedThreadPool

newFixedThreadPool 是 Executors 工厂类提供的一种固定大小的线程池,它创建的线程池具有固定数量的工作线程。具体来说,newFixedThreadPool 创建的线程池会维护一个固定数量的工作线程,当有任务提交时,会使用其中一个工作线程来执行任务。

这种线程池适用于以下场景:

  1. 控制最大并发数:需要限制并发执行的任务数量,以防止系统资源被过度占用。

  2. 资源限制:对于一些资源受限的场景,如数据库连接池、网络连接池等,可以使用固定大小的线程池来控制资源的分配和利用。

  3. 预测性能需求:在已知任务量和性能需求的情况下,可以根据需求预先配置固定数量的工作线程,以保证系统在高负载时仍能稳定运行。

在固定大小的线程池中,线程的数量是固定的,不会根据任务的增加或减少而自动调整。如果所有工作线程都正在执行任务,并且有新任务提交时,新任务会被放入队列中等待,直到有工作线程空闲出来才会被执行。

以下是一个简单的 Java 代码示例,演示了如何使用 newFixedThreadPool 创建固定大小的线程池,并提交任务执行:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class main {public static void main(String[] args) {// 创建一个固定大小为3的线程池ExecutorService executor = Executors.newFixedThreadPool(3);// 提交多个任务给线程池执行for (int i = 0; i < 5; i++) {final int taskId = i;executor.submit(() -> {System.out.println("任务 " + taskId + " 正在线程上运行: " + Thread.currentThread().getName());try {Thread.sleep(1000); // 模拟任务执行} catch (InterruptedException e) {Thread.currentThread().interrupt(); // 保持中断状态}});}// 关闭线程池executor.shutdown();}
}

在上面的示例中,我们通过 Executors.newFixedThreadPool(3) 方法创建了一个固定大小为3的线程池。然后,使用 executor.submit() 方法向线程池提交了5个任务,每个任务都会打印当前线程名并模拟执行一段时间。最后,通过 executor.shutdown() 关闭了线程池。

当你运行这段代码时,你会看到虽然提交了5个任务,但是只有3个任务会同时在不同的工作线程中执行,因为线程池的大小被限制为3。剩余的任务会在有线程空闲时依次执行。

需要注意的是,在使用固定大小的线程池时,应该根据系统资源和运行环境合理地配置线程数量,避免资源浪费或不足的情况发生。

总之,FixedThreadPool 用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量

newCachedThreadPool

newCachedThreadPool 是 Executors 工厂类提供的一种可缓存线程池,它创建的线程池会根据需要自动调整线程数量。具体来说,当有新任务提交时,如果当前有空闲线程,则会立即使用空闲线程执行任务;如果没有空闲线程,则会创建新的线程来处理任务。而对于长时间闲置未被使用的线程,它们会在一定的空闲时间后被回收,从而使得线程池的大小能够动态调整。

这种线程池适用于以下场景:

  1. 处理大量短时耗任务:适用于有大量短时耗的任务需要处理的情况,例如网络请求响应、小数据处理等。

  2. 并发需求不确定:当并发需求不确定,且系统资源充足时,可以使用可缓存线程池来动态调整线程数量,以适应不同负载情况。

  3. 短期高并发:对于突发性的高并发情况,可缓存线程池能够快速创建新线程来应对,从而保证任务的及时处理。

以下是一个简单的 Java 代码示例,演示了如何使用 newCachedThreadPool 创建可缓存线程池,并提交任务执行:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class main {public static void main(String[] args) {// 创建一个可缓存线程池ExecutorService executor = Executors.newCachedThreadPool();// 提交多个任务给线程池执行for (int i = 0; i < 5; i++) {final int taskId = i;executor.submit(() -> {System.out.println("任务 " + taskId + "正在线程上运行: " + Thread.currentThread().getName());try {Thread.sleep(1000); // 模拟任务执行} catch (InterruptedException e) {Thread.currentThread().interrupt(); // 保持中断状态}});}// 关闭线程池executor.shutdown();}
}

在上面的示例中,我们通过 Executors.newCachedThreadPool() 方法创建了一个可缓存线程池。然后,使用 executor.submit() 方法向线程池提交了5个任务,每个任务都会打印当前线程名并模拟执行一段时间。最后,通过 executor.shutdown() 关闭了线程池。

当你运行这段代码时,你会看到所有5个任务都会立即在不同的工作线程中执行,因为可缓存线程池会根据需要动态调整线程数量。这种机制能够很好地适应不同负载情况下的任务处理需求。

需要注意的是,由于可缓存线程池的特性是动态调整线程数量,因此在任务提交过多导致线程数激增时,需要谨慎控制以避免资源耗尽的情况发生。

总之,CachedThreadPool 用于并发执行大量短期的小任务,或者是负载较轻的服务器

newScheduledThreadPool

newScheduledThreadPool 是 Executors 工厂类提供的一种创建定时任务的线程池。它创建的线程池可以执行延迟任务和周期性任务,它会在给定的延迟时间后执行任务,或者定期执行任务。这种线程池适用于需要执行定时任务或者周期性任务的场景。

具体来说,newScheduledThreadPool 创建的线程池有以下特点:

  1. 支持延迟任务执行:可以使用 schedule() 方法提交一个任务,并指定延迟时间,线程池会在延迟时间过去后执行相应的任务。
  2. 支持周期性任务执行:可以使用 scheduleAtFixedRate() 或者 scheduleWithFixedDelay() 方法提交一个任务,并指定初始延迟时间和执行间隔,线程池会按照指定的间隔周期性执行任务。
  3. 灵活控制:可以通过 ScheduledExecutorService 提供的方法对定时任务进行取消、查询等操作,非常灵活方便。

在实际应用中,newScheduledThreadPool 可以用于定时数据同步、定时任务触发、定时器等场景,能够很好地满足对任务执行时间有特定要求的需求。

下面是一个简单的 Java 代码示例,演示了如何使用 newScheduledThreadPool 创建定时任务的线程池,并提交延迟任务和周期性任务:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class main {public static void main(String[] args) {// 创建一个定时任务线程池ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);// 提交延迟任务executor.schedule(() -> System.out.println("延迟的任务"), 2, TimeUnit.SECONDS);// 提交周期性任务executor.scheduleAtFixedRate(() -> System.out.println("周期性任务"), 0, 1, TimeUnit.SECONDS);// 等待一段时间后关闭线程池try {Thread.sleep(5000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}executor.shutdown();}
}

在上面的示例中,我们通过 Executors.newScheduledThreadPool(1) 方法创建了一个定时任务线程池。然后,使用 schedule() 方法提交了一个延迟任务,在2秒后执行;同时使用 scheduleAtFixedRate() 方法提交了一个周期性任务,每隔1秒执行一次。最后,我们等待一段时间后关闭了线程池。

当你运行这段代码时,你会看到延迟任务和周期性任务会按照预定的时间被执行。这展示了 newScheduledThreadPool 的定时任务执行能力。

总之,ScheduledThreadPoolExecutor 用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景

Executors和ThreaPoolExecutor创建线程池的区别

在 Java 中,确实存在着 Executors 工厂类的几种创建线程池的方法存在一些缺陷,让我们来具体分析一下。

1、newFixedThreadPool 和 newSingleThreadExecutor

这两种方法都使用了无界的 LinkedBlockingQueue 作为工作队列,在任务提交速度持续大于任务处理速度的情况下,可能会导致队列中累积大量的任务,从而消耗大量内存。如果任务量非常大或者任务执行时间非常长,那么这种设计就会面临 OutOfMemoryError (简称:OOM)的风险。

2、newCachedThreadPool 和 newScheduledThreadPool

这两种方法在某些情况下可能会创建大量的线程,因为它们的最大线程数是 Integer.MAX_VALUE。如果系统负载突然增加,那么就可能创建非常多的线程,从而消耗大量系统资源,甚至导致 OutOfMemoryError(简称:OOM)。

而相较之下,使用 ThreadPoolExecutor 构造函数创建线程池可以更好地控制线程池的行为,避免上述问题。通过 ThreadPoolExecutor 的构造函数,可以显式地指定核心线程数、最大线程数、工作队列类型、拒绝策略等参数,从而更精细地控制线程池的行为。

因此,在开发中,建议根据具体需求和系统负载情况,选择合适的线程池创建方法,并结合 ThreadPoolExecutor 来进行更为细致的参数配置,以避免潜在的问题。

两种提交任务的方法

ExecutorService 提供了两种提交任务的方法:

1、execute(Runnable command):

这个方法用于提交不需要返回值的任务。它接受一个 Runnable 对象作为参数,表示要执行的任务,但并不关心任务的返回结果。

ExecutorService executor = Executors.newFixedThreadPool(10);
executor.execute(new RunnableTask());

2、submit(Callable<T> task):

这个方法用于提交需要返回值的任务。它接受一个 Callable 对象作为参数,表示要执行的任务,并返回一个 Future 对象,通过它可以获取任务执行的结果。

ExecutorService executor = Executors.newFixedThreadPool(10);
Future<String> future = executor.submit(new CallableTask());
String result = future.get(); // 获取任务执行结果

通过这两种方法,可以灵活地提交不同类型的任务,并根据需要获取它们的执行结果。execute 方法适用于 fire-and-forget 的场景,而 submit 方法则更适合需要获取任务执行结果的情况。

以下是使用 ExecutorService 提供的 execute 和 submit 方法的示例代码:

import java.util.concurrent.*;public class main {public static void main(String[] args) throws InterruptedException, ExecutionException {// 创建一个固定大小为 3 的线程池ExecutorService executor = Executors.newFixedThreadPool(3);// 提交第一个任务Future<Integer> future1 = executor.submit(() -> {System.out.println("任务1正在运行");return 42;});// 提交第二个任务,并依赖于第一个任务的结果Future<Integer> future2 = executor.submit(() -> {int result = future1.get(); // 等待第一个任务的执行结果System.out.println("任务2正在根据任务1的结果运行: " + result);return result * 2;});// 提交第三个任务,并依赖于第二个任务的结果executor.execute(() -> {try {int result = future2.get(); // 等待第二个任务的执行结果System.out.println("任务3正在根据任务2的结果运行:" + result);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}});// 关闭线程池executor.shutdown();}
}

在这个示例中,我们使用submit方法提交了第一个和第二个任务,并使用execute方法提交了第三个任务。第二个任务依赖于第一个任务的结果,而第三个任务则依赖于第二个任务的结果。通过这种方式,我们展示了如何在复杂情况下使用ExecutorService的execute和submit方法来提交具有相互依赖关系的任务。

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

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

相关文章

Leetcode2834. 找出美丽数组的最小和

Every day a Leetcode 题目来源&#xff1a;2834. 找出美丽数组的最小和 解法1&#xff1a;贪心 从最小正整数 1 开始枚举&#xff0c;设当前数为 num&#xff0c;如果 nums 里没有 target - num&#xff0c;就说明可以添加 num&#xff0c;依次填满直到有 n 个数即可。 用…

【k8s-1】基于docker Desktop一键式搭建k8s环境

在docker desktop中一键启动k8s环境很简单。 下面介绍如何启动dashboard&#xff0c;dashboard仪表盘是新手学习k8s至关重要的一个工具。 1、配置控制台 kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.5.1/aio/deploy/recommended.yaml 2、开…

k8s的安装部署,详细过程展示(保姆级安装教程)

k8s应用部署方式演变 在部署应用程序的方式上&#xff0c;主要经历了三个时代&#xff1a; 传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上 优点&#xff1a;简单&#xff0c;不需要其它技术的参与 缺点&#xff1a;不能为应用程序定义资源使用…

Java面向对象(进阶)-- 面向对象特征之三:多态性

文章目录 一、多态的形式和体现&#xff08;1&#xff09;为什么需要多态性(polymorphism)&#xff1f;&#xff08;2&#xff09; 对象的多态性 二、 多态的理解&#xff08;1&#xff09;如何理解多态性&#xff08;2&#xff09;Java中多态性的体现&#xff08;3&#xff09…

数据分析实战 | KNN算法——病例自动诊断分析

目录 一、数据及分析对象 二、目的及分析任务 三、方法及工具 四、数据读入 五、数据理解 六、数据准备 七、模型训练 八、模型评价 九、模型调参 十、模型改进 十一、模型预测 一、数据及分析对象 CSV文件——“bc_data.csv” 数据集链接&#xff1a;https://dow…

说说 React 性能优化的手段有哪些?

一、是什么 React凭借virtual DOM和diff算法拥有高效的性能&#xff0c;但是某些情况下&#xff0c;性能明显可以进一步提高 在前面文章中&#xff0c;我们了解到类组件通过调用setState方法&#xff0c; 就会导致render&#xff0c;父组件一旦发生render渲染&#xff0c;子组…

LeetCode146.LRU缓存

写了一个小时&#xff0c;终于把示例跑过了&#xff0c;没想到啊提交之后第19/22个测试用例没过 我把测试用例的输出复制在word上看看和我的有什么不同&#xff0c;没想到有18页的word&#xff0c;然后我一直检查终于找出了问题&#xff0c;而且这个bug真的太活该了&#xff0c…

Rocky Linux 配置邮件发送

Rocky Linux 配置邮件发送 使用自己的有邮箱发送 第一步-开启STMP授权 首先要开启STMP授权码&#xff0c;以QQ邮箱为例 第二步-下载安装包 说明一点不用命令行安装也可以&#xff0c;在命令行中输入会提示你是否安装s-nail&#xff0c;一直y即可 mail下载必须要的安装包 …

在ubuntu sudo apt-get update 更新报错

sudo apt-get update 更新报错 解决办法&#xff1a; 用你自己的key 根据上图自己找 sudo gpg --keyserver keyserver.ubuntu.com --recv-keys **********运行完成有一个ok 见下图 运行命令&#xff0c;中间的还是上面的key复制下来即可 sudo gpg --export --armor **********…

【华为OD机试AB高分必刷题目】拆分(Python-贪心算法实现)

🚀你的旅程将在这里启航!本专栏所有题目均包含优质解题思路,高质量解题代码,详细代码讲解,助你深入学习,高分通过! 文章目录 【华为OD机试AB高分必刷题目】拆分(Python-贪心算法实现)题目描述解题思路Python题解代码代码OJ评判结果代码讲解寄语【华为OD机试AB高分必刷…

Android 多点触控

三种类型 :接力型 /配合型 /单独型 单点触控 package com.example.myapplication.viewimport android.content.Context import android.graphics.Canvas import android.graphics.Paint import android.util.AttributeSet import android.view.MotionEvent import android.vi…

HTTPS 的工作原理是什么?

HTTPS&#xff08;HyperText Transfer Protocol Secure&#xff09;是一种通过加密和认证保护数据传输安全的通信协议。它是基于传统的 HTTP 协议&#xff0c;通过使用 SSL&#xff08;Secure Sockets Layer&#xff09;或 TLS&#xff08;Transport Layer Security&#xff09…

mysql explain extra值枚举

Extra 值说明Using index仅使用索引进行数据查询Using where在取得所需数据前使用了 WHERE 子句进行过滤Using temporary使用了临时表来保存中间结果Using filesort查询中使用了文件排序Using join buffer告诉MySQL在内存中建立了一个表连接缓冲区以容纳连接类型为索引的行。Im…

CHATGPT----自然辩证法分析

CHATGPT----自然辩证法的要素&#xff0c;结构与功能 Chatgpt的要素组成&#xff1a; ChatGPT的构成主要包括语言模型、对话管理、知识库和用户接口等几个方面。 语言模型&#xff1a;ChatGPT的核心是语言模型&#xff0c;它是一种基于深度学习技术的自然语言处理模型&#…

IntelliJ IDEA 2023.2.1 (Ultimate Edition) 版本 Git 如何找回被 Drop Commit 的提交记录

本心、输入输出、结果 文章目录 IntelliJ IDEA 2023.2.1 (Ultimate Edition) 版本 Git 如何找回被 Drop Commit 的提交记录前言查询 reflog 日志通过 Git Reset HEAD (hard) 找回已经 Drop Commit 的提交记录Git Reset HEAD (hard) 模式和 mixed 模式有啥区别git reset --h…

10 # 手写 every 方法

every 使用 every() 方法测试一个数组内的所有元素是否都能通过指定函数的测试。它返回一个布尔值。 ele&#xff1a;表示数组中的每一个元素index&#xff1a;表示数据中元素的索引array&#xff1a;表示数组 <script>var arr [1, 3, 5, 7, 8];var result arr.ever…

win 命令替代鼠标的操作

操作方式都是在 winR 输入框输入或者终端输入 1、快速打开 控制面板 运行control 2、快速打开 电源选项 运行powercfg.cpl 3、快速打开 网络连接 运行ncpa.cpl 4、快速打开 程序和功能 运行appwiz.cpl 5、快速打开 Windows Defender防火墙 运行Firewall.cpl 6、快速打开 鼠标 …

Matlab的多项式留数与极点的计算

Matlab的多项式留数与极点的计算 以下面的多项式为例&#xff1a; 运算代码&#xff1a; clc clear closesyms p % 定义多项式 Zp(5*p^571*p^370*p)/(2*p^635*p^4117*p^236); % 提取分子与分母 [I,D]numden(Zp); Idouble(coeffs(I,p,"All"));%分子 Ddouble(coeffs…

多目标优化框架

随着模型越来越复杂&#xff0c;优化目标越来越多&#xff0c;传统算法都慢慢地无法胜任复杂优化任务&#xff0c;更为智能的优化方法也就应运而生了。其中有一类是进化优化算法&#xff0c;这类算法的思想来源是自然界的“优胜劣汰”法则&#xff0c;通过不停地保留好的个体最…

大漠插件(二、Qt使用插件时注意事项)

本章目的 在上篇已经注册完毕大漠&#xff0c;那么怎么使用大漠来制作脚本&#xff0c;我选择了我最熟悉的Qt来开发&#xff0c;毕竟只是小软件&#xff0c;用脚本或者c都差不了多少。本章就是开发途中的一些坑。 本人开发环境是 win11 64、Qt 5.15.2安装了5.10.0的msvc2015 32…