使用ExecutorService和@Async来使用多线程

news2025/1/5 20:08:35

文章目录

  • 使用ExecutorService和@Async来使用多线程
    • 采用ExecutorService来使用多线程
      • 多线程过程的详细解释
      • 注意事项
      • 优点
    • 使用@Async来使用多线程
    • 对比@Async和ExecutorService的多线程使用方式
      • 使用 `ExecutorService` 的服务类
      • 使用 `@Async` 的服务类
      • 异步任务类
      • 自定义线程池
      • 主应用类
      • 解释
      • 运行这个示例
      • 注意点:

使用ExecutorService和@Async来使用多线程

采用ExecutorService来使用多线程

我们直接来通过一段代码来看一下多线程的过程,大致就是通过多线程来生成图片

@Service
public class NameService {

    private final ExecutorService executorService = Executors.newFixedThreadPool(10); // 创建一个固定大小的线程池

    public void createImg(String fileNamePrefix) {
        // 假设我们有一组需要生成的图片
        List<String> imagePrefixes = getImagePrefixes(fileNamePrefix);

        try {
            for (String prefix : imagePrefixes) {
                // 提交任务
                executorService.submit(() -> {
                    generateImage(prefix); // 生成图片的方法
                });
            }
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭线程池,但允许已提交的任务继续执行
            executorService.shutdown();
            // 下面是为了确保线程池关闭,手动设置一个时间,如果线程60秒没有执行完,则强制关闭该线程,我这里不需要
			// try {
			// if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
            // 	executorService.shutdownNow();
            // }
            // } catch (InterruptedException e) {
            // 	executorService.shutdownNow();
            // 	Thread.currentThread().interrupt();
            // }
        }

    }

    // 模拟要生成的图片名列表
    private List<String> getImagePrefixes(String fileNamePrefix) {
        // 获取需要生成的图片前缀列表,示例代码
        return Arrays.asList(fileNamePrefix + "_1", fileNamePrefix + "_2", fileNamePrefix + "_3");
    }

    // 生成图片的方法
    private void generateImage(String prefix) {
        // 生成图片并保存到本地的方法,示例代码
        System.out.println("Generating image for prefix: " + prefix);
        // 实际的图片生成逻辑,这里简单用一个延时函数代替
        try { 
            Thread.sleep(1000); // 模拟生成图片的时间 
        } catch (InterruptedException e) { 
            Thread.currentThread().interrupt(); 
        } 
        System.out.println("Generating image for prefix: " +
    }
}

// 注释相关解释:
//awaitTermination:等待所有任务在指定时间内完成。如果在指定时间(60 秒)内所有任务没有完成,则调用 executorService.shutdownNow() 强制关闭线程池,并中断所有正在执行的任务。
//异常处理:处理可能的 InterruptedException,确保线程池能够被正确关闭。

多线程过程的详细解释

  1. 创建线程池: 使用 Executors.newFixedThreadPool(10) 创建一个固定大小为 10 的线程池。你可以根据需要调整线程池的大小。
  2. 提交任务: 使用 executorService.submit() 方法将图片生成任务提交给线程池,这些任务会被放入线程池的任务队列中,等待线程池中的线程来执行。
  3. 执行任务: 线程池中的线程会从任务队列中取出任务并执行。使用的是 ExecutorService 和多线程,即使 for 循环结束,提交的任务仍会在后台继续运行,直到所有任务完成。
  4. 关闭线程池: 调用 executorService.shutdown() 方法后,线程池不再接受新任务,但会继续执行已经提交的任务,直到所有任务执行完毕。如果 generateImage 方法耗时较长,那么这些任务会在后台继续运行,直到所有任务完成。

多线程使得任务的提交与执行分离。for 循环快速提交任务,而线程池在后台并发地执行这些任务。这种异步执行的机制允许你同时处理多个任务,而不需要等待每个任务的完成。

注意事项

  • 任务数量:确保线程池大小与任务数量合理匹配,以免线程过多或过少影响性能。
  • 异常处理:可以在任务中添加异常处理逻辑,确保不会因为某个任务失败而影响其他任务的执行。

优点

  • 提高并发性:多个任务可以同时进行,提高效率。
  • 主线程不中断:主线程可以继续执行其他操作,而不需要等待每个任务的完成。

使用@Async来使用多线程

Spring 提供了 @Async 注解,用于简化多线程编程。通过这个注解让 Spring 自动管理线程池。

启用异步支持: 首先,在 Spring Boot 应用中启用异步支持。可以在主应用类或配置类上添加 @EnableAsync 注解。

标记异步方法: 在需要异步执行的方法上添加 @Async 注解。Spring 会在后台线程池中执行这些方法,不会阻塞主线程。

具体例子咱们可以通过下面的对比来看。

对比@Async和ExecutorService的多线程使用方式

使用 ExecutorService 的服务类

package caj.springboot.service;

import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.List;
import java.util.Arrays;

/**
 * @description: 使用 `ExecutorService` 的服务类
 * @author: CAJ
 * @time: 2025/1/2 20:13
 */
@Service
public class ExecutorServiceNameService {

    private final ExecutorService executorService = Executors.newFixedThreadPool(10); // 创建一个固定大小的线程池

    public void createImg(String fileNamePrefix) {
        List<String> imagePrefixes = getImagePrefixes(fileNamePrefix);

        long startTime = System.currentTimeMillis();

        for (String prefix : imagePrefixes) {
            executorService.submit(() -> {
                generateImage(prefix); // 生成图片的方法
            });
        }
        executorService.shutdown();

        // 用来判断线程池中的所有任务是否都已经完成。
        while (!executorService.isTerminated()) {
            // 等待所有任务完成
        }

        long endTime = System.currentTimeMillis();
        System.out.println("ExecutorService 总耗时: " + (endTime - startTime) + " 毫秒");
    }

    private List<String> getImagePrefixes(String fileNamePrefix) {
        return Arrays.asList(fileNamePrefix + "_1", fileNamePrefix + "_2", fileNamePrefix + "_3");
    }

    private void generateImage(String prefix) {
        try {
            Thread.sleep(1000); // 模拟生成图片的时间
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Generating image for prefix: " + prefix);
    }
}

使用 @Async 的服务类

(之后将这个异步方法抽出来了,这里面只是调用,否则无法异步执行)

package caj.springboot.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

/**
 * @description:
 * @author: CAJ
 * @time: 2025/1/2 20:13
 */
@Service
public class AsyncNameService {
    @Autowired
    private AsyncTask asyncTask;

    public void createImg(String fileNamePrefix) {
        List<String> imagePrefixes = getImagePrefixes(fileNamePrefix);

        long startTime = System.currentTimeMillis();

        // 下面这样写只是为了确保整个异步过程结束才会记录endTime,正常使用就for循环然后调用方法即可
        List<CompletableFuture<Void>> futures = imagePrefixes.stream()
                .map(prefix -> asyncTask.generateImageAsync(prefix))
                .collect(Collectors.toList());
        // 等待所有异步任务完成
        CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
        try {
            allOf.get(); // 阻塞等待所有任务完成
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        long endTime = System.currentTimeMillis();
        System.out.println("Async 总耗时: " + (endTime - startTime) + " 毫秒");
    }


    private List<String> getImagePrefixes(String fileNamePrefix) {
        return Arrays.asList(fileNamePrefix + "_1", fileNamePrefix + "_2", fileNamePrefix + "_3");
    }

//    本来将异步方法直接在这个类中写,但是发现无论如何都无法异步执行,顾后面将异步方法抽出来
//    /**
//     * 在异步方法 generateImageAsync 中返回一个 CompletableFuture,表示异步任务的完成。
//     * @param prefix
//     * @return
//     */
//    @Async("taskExecutor")
//    public CompletableFuture<Void> generateImageAsync(String prefix) {
//        System.out.println(Thread.currentThread().getName() + " is processing " + prefix);
//        generateImage(prefix);
//        return CompletableFuture.completedFuture(null);
//    }
//
//
//    private void generateImage(String prefix) {
//        try {
//            Thread.sleep(1000); // 模拟生成图片的时间
//        } catch (InterruptedException e) {
//            Thread.currentThread().interrupt();
//        }
//        System.out.println("Generating image for prefix: " + prefix);
//    }
}

异步任务类

package caj.springboot.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.concurrent.CompletableFuture;

/**
 * @description: 异步任务类
 * @author: CAJ
 * @time: 2025/1/2 20:52
 */
@Component
public class AsyncTask {

    @Async("taskExecutor")
    public CompletableFuture<Void> generateImageAsync(String prefix) {
        generateImage(prefix);
        return CompletableFuture.completedFuture(null);
    }

    private void generateImage(String prefix) {
        try {
            Thread.sleep(1000); // 模拟生成图片的时间
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Generating image for prefix: " + prefix);
    }
}

自定义线程池

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("Async-");
        executor.initialize();
        return executor;
    }
}
  1. 自定义线程池配置类:通过实现 AsyncConfigurer 接口和定义 getAsyncExecutor 方法,配置自定义的线程池。
  2. 自定义线程池参数:设置核心线程数、最大线程数、队列容量和线程名前缀。

使用 @Async 注解和自定义线程池,你可以轻松地将方法异步执行,从而提高并发处理能力并优化应用程序性能。

主应用类

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class AsyncDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(AsyncDemoApplication.class, args);
    }

    @Bean
    CommandLineRunner run(ExecutorServiceNameService executorServiceNameService, AsyncNameService asyncNameService) {
        return args -> {
            System.out.println("使用 ExecutorService:");
            executorServiceNameService.createImg("test");

            System.out.println("使用 @Async:");
            asyncNameService.createImg("test");
        };
    }
}
运行结果:
使用 ExecutorService:
Generating image for prefix: test_2
Generating image for prefix: test_1
Generating image for prefix: test_3
ExecutorService 总耗时: 1003 毫秒
使用 @Async:
Generating image for prefix: test_2
Generating image for prefix: test_1
Generating image for prefix: test_3
Async 总耗时: 1009 毫秒
由于这里的例子设置时间比较短,所以最终时间差不多

解释

  1. ExecutorServiceNameService:使用 ExecutorService 处理图片生成任务,创建一个固定大小为 10 的线程池。
  2. AsyncNameService:使用 @Async 注解让方法异步执行,依赖 Spring 管理的线程池。
  3. 主应用类:在应用启动时,运行两个服务并输出各自的总耗时,以便对比。

运行这个示例

  • 将这两个服务类和主应用类放入项目中,运行主应用程序。
  • 程序会分别调用 ExecutorServiceNameServiceAsyncNameServicecreateImg 方法,并输出各自的总耗时。

通过这个对比,你可以清楚地看到使用 ExecutorService@Async 的不同之处以及它们的性能表现。

注意点:

  1. 为了确保 @Async 注解生效,可以将异步方法放在一个独立的类中,并从主服务类调用这个异步方法。这样,Spring AOP 代理能够正确拦截方法调用并应用异步特性,因为Spring 使用 AOP 代理来拦截方法调用并提供额外的功能,比如事务管理、异步执行等,
  2. 同一个类内部自调用:当你在同一个类内部调用带有 @Async 注解的方法时,Spring AOP 代理无法拦截这个调用,从而无法应用异步特性。这是因为代理对象拦截方法调用的前提是调用发生在代理对象本身上,而不是在同一个类内部。

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

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

相关文章

2024年中国新能源汽车用车发展怎么样 PaperGPT(二)

用车趋势深入分析 接上文&#xff0c;2024年中国新能源汽车用车发展怎么样 PaperGPT&#xff08;一&#xff09;-CSDN博客本文将继续深入探讨新能源汽车的用车强度、充电行为以及充电设施的现状。 用车强度 月均行驶里程&#xff1a;2024年纯电车辆月均行驶超过1500公里&…

antd-vue - - - - - a-date-picker限制选择范围

antd-vue - - - - - a-date-picker限制选择范围 1. 效果展示2. 代码展示 1. 效果展示 如图&#xff1a;限制选择范围为 今年 & 去年 的 月份. 2. 代码展示 <template><a-date-picker:disabledDate"disabledDate"picker"month"/> &l…

滑动窗口、流量控制和拥塞控制

1. 确认应答机制 确认应答机制是计算机网络中&#xff0c;用于确保数据可靠传输的一种方法。 它通过发送 ACK 数据段来通知对方&#xff0c;每一个 ACK 数据段都有一个确认序号&#xff0c;表明&#xff1a; 确认序号之前的所有数据都已被接收&#xff0c;接下来从确认序号开…

TCP粘/拆包----自定义消息协议

今天是2024年12月31日&#xff0c;今年的最后一天&#xff0c;希望所有的努力在新的一年会有回报。❀ 无路可退&#xff0c;放弃很难&#xff0c;坚持很酷 TCP传输 是一种面向二进制的&#xff0c;流的传输。在传输过程中最大的问题是消息之间的边界不明确。而在服务端主要的…

前端,npm install安装依赖卡在sill idealTree buildDeps(设置淘宝依赖)

输入npm i后&#xff0c;一直卡在sill idealTree buildDeps&#xff0c;一动不动 cnpm可以安装成功&#xff0c;但使用cnpm不会生成package-lock.json文件 设置淘宝依赖&#xff0c;依然卡住&#xff0c;挂梯子也不行 解决方法&#xff1a; // 取消ssl验证 set strict-ssl …

【有作图代码】Highway Network与ResNet:skip connection如何解决深层网络欠拟合问题

【有作图代码】Highway Network与ResNet&#xff1a;skip connection如何解决深层网络欠拟合问题 关键词&#xff1a; #Highway Network #ResNet #skip connection #深层网络 #欠拟合问题 具体实例与推演 假设我们有一个深层神经网络&#xff0c;其层数为L&#xff0c;每一…

目标检测入门指南:从原理到实践

目录 1. 数据准备与预处理 2. 模型架构设计 2.1 特征提取网络原理 2.2 区域提议网络(RPN)原理 2.3 特征金字塔网络(FPN)原理 2.4 边界框回归原理 2.5 非极大值抑制(NMS)原理 2.6 多尺度训练与测试原理 2.7 损失函数设计原理 3. 损失函数设计 4. 训练策略优化 5. 后…

搭建开源版Ceph分布式存储

系统&#xff1a;Rocky8.6 三台2H4G 三块10G的硬盘的虚拟机 node1 192.168.2.101 node2 192.168.2.102 node3 192.168.2.103 三台虚拟机环境准备 1、配置主机名和IP的映射关系 2、关闭selinux和firewalld防火墙 3、配置时间同步且所有节点chronyd服务开机自启 1、配置主机名和…

租用服务器还是服务器托管:哪种方案更适合您?

随着企业对网络服务质量要求的不断提高&#xff0c;租用服务器和服务器托管是两种常见的选择&#xff0c;各自具备独特的优势和适用场景。这篇文章将从多个维度对这两种方案进行详细分析&#xff0c;帮助大家进行对比选择。 租用服务器的优劣势分析 优点 无需大额初始投入 租用…

LDD3学习6--Scull的变种

1 整体介绍 之前在LDD3学习1里面就提过scull的变种&#xff0c;LDD学习1--启程-CSDN博客&#xff0c;大概的变种有这些&#xff1a; 名称全名说明对应章节scullSimple Character Utility for Loading Localities基础版本3scullcScull with Slab cache使用基于slab高速缓存8.2.…

设计模式の状态策略责任链模式

文章目录 前言一、状态模式二、策略模式三、责任链模式 前言 本篇是关于设计模式中的状态模式、策略模式、以及责任链模式的学习笔记。 一、状态模式 状态模式是一种行为设计模式&#xff0c;核心思想在于&#xff0c;使某个对象在其内部状态改变时&#xff0c;改变该对象的行为…

【网络协议】路由信息协议 (RIP)

未经许可&#xff0c;不得转载。 路由信息协议&#xff08;Routing Information Protocol&#xff0c;简称 RIP&#xff09;是一种使用跳数&#xff08;hop count&#xff09;作为路由度量标准的路由协议&#xff0c;用于确定源网络和目标网络之间的最佳路径。 文章目录 什么是…

linux下安装达梦数据库v8详解

目录 操作系统、数据库 1、下载达梦数据库 2、安装前准备 2.1、建立数据库用户和组 2.2、修改文件打开最大数 2.3、挂载镜像 2.4、新建安装目录 3、数据库安装 4、配置环境变量 5、初始化数据库实例 6、注册服务 7、使用数据库 8、卸载数据库 9、多实例管理 10、…

小程序租赁系统的优势与应用探索

内容概要 小程序租赁系统&#xff0c;听起来很高大上&#xff0c;但实际上它比你想象的要实用得多&#xff01;设想一下&#xff0c;几乎所有的租赁需求都能通过手机轻松解决。这种系统的便捷性体现在让用户随时随地都能发起租赁请求&#xff0c;而不再受制于传统繁琐的手续。…

(leetcode算法题)​122. 买卖股票的最佳时机 II​ 和 123. 买卖股票的最佳时机 III

这两个题都可以进行转化&#xff0c;转换成等价问题求解 对于122的等价转换 求出所有能够赚钱的区间&#xff0c;这些区间满足一下特点 1. 首尾相接&#xff0c; 2. 区间末尾的值大于区间开头的值 3. 每个区间尽可能的小 新的问题只要用贪心的思想就能求得问题的解 只要求出上…

oceanbase集群访问异常问题处理

1.报错现象 2.问题排查 检查obproxy状态发现为不可用状态 重启obproxy 依次重启Obproxy集群 观察任务状态 重启完成 Obproxy状态正常 3.验证登录 登录成功

WeNet:面向生产的流式和非流式端到端语音识别工具包

这篇文章介绍了WeNet&#xff0c;一个面向生产的开源端到端&#xff08;E2E&#xff09;语音识别工具包。WeNet的主要特点和贡献如下&#xff1a; 统一流式和非流式识别&#xff1a;提出了一种名为U2的两阶段框架&#xff0c;能够在单一模型中同时支持流式和非流式语音识别&…

ArcGIS计算矢量要素集中每一个面的遥感影像平均值、最大值等统计指标

本文介绍在ArcMap软件中&#xff0c;基于矢量面要素集&#xff0c;计算在其中每一个面区域内&#xff0c;遥感影像的像元个数、平均值、总和等统计值&#xff0c;并将统计信息附加到矢量图层的属性表中的方法。 首先&#xff0c;明确一下本文的需求。现在有一个矢量面要素集&am…

Wallpaper壁纸制作学习记录13

骨骼物理模拟 Wallpaper Engine还允许您为人偶变形骨骼配置某些物理模拟。选择骨骼时&#xff0c;点击编辑约束来配置骨骼这些属性。 警告 请记住&#xff0c;物理模拟可能会根据用户的最大FPS设置略微改变其行为。 Wallpaper Engine编辑器将始终以高帧速率渲染。您可以将壁纸…

CertiK《Hack3d:2024年度安全报告》(附报告全文链接)

CertiK《Hack3d&#xff1a;2024年度安全报告》现已发布&#xff0c;本次报告深入分析了2024年Web3.0领域的安全状况。2024年损失总额超过23亿美元&#xff0c;同比增幅高达31.61%&#xff1b;其中&#xff0c;12月的损失金额最少。过去一年&#xff0c;网络钓鱼攻击和私钥泄露…