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

news2024/11/24 3:11:53

目录

线程池架构 

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.coloradmin.cn/o/1180066.html

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

相关文章

手术训练系统项目

★ 手术训练系统项目 项目描述&#xff1a;手术训练系统&#xff0c;它提供了多项功能&#xff0c;包括账户登录与创建、数据库与账户管理、课程管理、小组管理、成绩统计、证书发布、训练和系统设置。 职责描述: 1、训练功能开发&#xff08;任务概述、任务指导、评分规则、评…

惊艳!拓世法宝AI智能数字人一体机解锁数字文博的全民体验

在数字化的潮流中&#xff0c;我们见证了历史与现代技术的完美融合。在今年的“国际古迹遗址日”&#xff0c;世界首个超时空参与式博物馆“数字藏经洞”正式与公众见面&#xff0c;在这里开启了一场前所未有的文化探索之旅。 时间和空间被艺术化的数字技术巧妙地折叠&#xf…

建链时,please install openssl! use “openssl version“ command to check.

please install openssl! use “openssl version” command to check. 但是我已经安装了 编辑build_chain.sh文件 也可以用vi或者gedit命令 将 [ ! -z “ ( o p e n s s l v e r s i o n ∣ g r e p 1.0.2 ) " ] ∣ ∣ [ ! − z " (openssl version | grep 1.0.2)…

YOLO目标检测——红绿灯检测数据集【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;红绿灯检测数据集在自动驾驶、交通安全监控、智能交通系统、交通流量监测和驾驶员辅助系统等领域都有广泛应用的潜力数据集说明&#xff1a;红绿灯检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富&#xff0c;含有国内红绿灯…

ARM64 linux并发与同步之原子操作

卷2&#xff1a;调试与案例分析 第一章 并发与同步 画了两张简图&#xff0c;方便理解&#xff0c;如下&#xff1a; 针对并发源的问题&#xff0c;我接触的项目中都是SMP系统&#xff0c;目前大部分也都是SMP系统&#xff1b; 对于SMP系统&#xff0c;情况会更复杂。 □ 同…

第二证券:特斯拉将推出低价电动汽车?最新消息

当地时间周一&#xff0c;投资者接连上星期五的达观心境&#xff0c;美国三大股指高开高走。美联储主席鲍威尔本周到会活动时将有两次揭穿说话&#xff0c;投资者期望从中得到更多关于本轮加息周期完毕的信号&#xff0c;但也对联邦基金利率可能在更长时期内维持在高水平保持警…

我的创作纪念日——2048天

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

Java并发工具-1-原子操作(Atomic)

一 原子操作之 AtomicInteger 1 概念解释 什么是原子操作呢&#xff1f;所谓原子操作&#xff0c;就是一个独立且不可分割的操作。 AtomicInteger 工具类提供了对整数操作的原子封装。为什么要对整数操作进行原子封装呢&#xff1f; 在 java 中&#xff0c;当我们在多线程情…

龙芯loongarch64服务器编译安装matplotlib

前言 根据我之前的文章介绍&#xff0c;龙芯loongarch64服务器中的很多python依赖包安装有问题&#xff0c;发现其中安装的"matplotlib"就无法正常使用&#xff0c;或报如下错误&#xff1a;ImportError: cannot import name _c_internal_utils from partially initi…

python 中文字符转换unicode及Unicode 编码转换为中文

废话不多说 直接开干 知识点 decode 字节编码可decode为str encode 将字符串转换为bytes类型的对象 (即b为前缀, bytes类型), 即Ascll编码, 字节数组 encode(‘unicode-escape’)可将此str编码为bytes类型, 内容则是unicode形式 decode(‘unicode-escape’)可将内容为unicode形…

智能座舱“卷“疯了!8295不再是最“亮”点,还需要这些顶级配置

前段时间&#xff0c;车圈被两款“极”字辈的新车刷屏&#xff1a;极越01和极氪001 FR。 前者可视作当前自主品牌车企高度智能化水平的“范式”&#xff0c;后者无疑是中国汽车品牌颠覆极限性能的代表作。 同时&#xff0c;这两款车将座舱配置作为卖点进行了详细推送&#xf…

iview table 表格合并单元格

一、如图所示 二、实现方式 表格用提供的span-method属性 <template><Table ref"table" border :span-method"handleSpan" :row-key"true" :columns"tableColumns" :data"tableData"no-data-text"暂无数据&…

NOIP2023模拟12联测33 B. 游戏

NOIP2023模拟12联测33 B. 游戏 文章目录 NOIP2023模拟12联测33 B. 游戏题目大意思路code 题目大意 期望题 思路 二分答案 m i d mid mid &#xff0c;我们只关注学生是否能够使得被抓的人数 ≤ m i d \le mid ≤mid 那我们就只关心 a > m i d a > mid a>mid 的房…

HTML的初步学习

HTML HTML 描述网页的骨架, 标签化的语言. HTML 的执行是浏览器的工作,浏览器会解析 html 的内容,根据里面的代码,往页面上放东西,浏览器的工作归根结底,还是以汇编的形式在CPU上执行. 浏览器对于html语法格式的检查没有很严格,即使你写的代码有一些不合规范之处,浏览器也会尽可…

ZZ308 物联网应用与服务赛题第E套

2023年全国职业院校技能大赛 中职组 物联网应用与服务 任 务 书 &#xff08;E卷&#xff09; 赛位号&#xff1a;______________ 竞赛须知 一、注意事项 1.检查硬件设备、电脑设备是否正常。检查竞赛所需的各项设备、软件和竞赛材料等&#xff1b; 2.竞赛任务中所使用的…

系列十九、使用JDK生成HTTPS证书

一、HTTPS概述 历史上&#xff0c;HTTPS 连接经常用于网络上的交易支付和企业信息系统中敏感信息的传输。在 2000 年代末至 2010 年代初&#xff0c;HTTPS 开始广泛使用&#xff0c;以确保各类型的网页真实&#xff0c;保护账户和保护用户通信&#xff0c;身份和网络浏览的私密…

家政预约服务小程序源码系统 线上+线下两种模式 带完整的搭建教程

人们生活水平的不断提高&#xff0c;使得家政服务行业逐渐成为一个重要的行业。然而&#xff0c;传统的家政服务模式存在一些问题&#xff0c;如信息不对称、服务质量不稳定等。为了解决这些问题&#xff0c;开发一款家政预约服务小程序源码系统变得尤为重要。下面源码小编来给…

Unity 声音的控制

闲谈&#xff1a; 游戏开发比普通软件开发难也是有原因的&#xff0c;第一 游戏功能需求变化多样内部逻辑交错纵横&#xff0c; 而软件相对固定&#xff0c;无非也就是点击跳转、数据存储 第二&#xff0c;游戏需要很多3D数学知识、物理知识&#xff0c;最起码得有高中物理的基…

Python开发运维:Python3.7安装Django3.2

目录 一、理论 1.pip 2.Django 3.Pycharm国内镜像源 二、实验 1.Python3.7安装Django3.2 三、问题 1.安装django3.2报错 2.pip更新报错 一、理论 1.pip &#xff08;1&#xff09;概念 1&#xff09;pip pip 是 Python 的包安装程序。其实&#xff0c;pip 就是 Pyt…

应用程序无法启动,因为应用程序的并行配置不正确。有关详细信息,请参阅应用程序事件日志,或使用命令行 sxstrace.exe 工具。

谷歌浏览器出现以下问题 解决 点击以下 new_chrome.exe 就可以了&#xff08; new_chrome.exe 点击之后就消失了&#xff09;