实现异步的9种方式,你知道几个?

图片

来源 | 捡田螺的小男孩(ID:gh_51e0e901a289)

前言

大家好,我是田螺。

我们日常开发的时候,经常说到异步编程。比如说,在注册接口,我们在用户注册成功时,用异步发送邮件通知用户。

那么,小伙伴们,你知道实现异步编程,一共有多少种方式吗?我给大家梳理了9种~~

  • 使用Thread和Runnable

  • 使用Executors提供线程池

  • 使用自定义线程池

  • 使用Future和Callable

  • 使用CompletableFuture

  • 使用ForkJoinPool

  • Spring的@Async异步

  • MQ实现异步

  • 使用Hutool工具库的ThreadUtil

1. 使用Thread和Runnable

Thread和Runnable是最基本的异步编程方式,直接使用Thread和Runnable来创建和管理线程。

public class Test {    public static void main(String[] args) {        System.out.println("田螺主线程:" + Thread.currentThread().getName());        Thread thread = new Thread(() -> {            try {                Thread.sleep(1000);                System.out.println("田螺异步线程测试:"+Thread.currentThread().getName());            } catch (InterruptedException e) {                e.printStackTrace();            }        });        thread.start();    }}

但是呢,日常工作中,不推荐直接使用Thread和Runnable,因为它有这些缺点:

  • 资源消耗大:每次创建新线程会消耗系统资源,频繁创建和销毁线程会导致性能下降。

  • 难以管理:手动管理线程的生命周期、异常处理、任务调度等非常复杂。

  • 缺乏扩展性:无法轻松控制并发线程的数量,容易导致系统资源耗尽。

  • 线程复用问题:每次任务都创建新线程,无法复用已有的线程,效率低下。

2.使用Executors提供线程池

针对Thread和Runnable的缺点,我们可以使用线程池呀,线程池主要有这些优点:

  • 它帮我们管理线程,避免增加创建线程和销毁线程的资源损耗。因为线程其实也是一个对象,创建一个对象,需要经过类加载过程,销毁一个对象,需要走GC垃圾回收流程,都是需要资源开销的。

  • 提高响应速度。如果任务到达了,相对于从线程池拿线程,重新去创建一条线程执行,速度肯定慢很多。

  • 重复利用。线程用完,再放回池子,可以达到重复利用的效果,节省资源。

有些小伙伴说,我们可以直接使用Executors提供的线程池呀,非常快捷方便,比如:

- Executors.newFixedThreadPool- Executors.newCachedThreadPool

简单demo代码如下:

public class Test {    public static void main(String[] args) {        System.out.println("田螺主线程:" + Thread.currentThread().getName());        ExecutorService executor = Executors.newFixedThreadPool(3);                executor.execute(()->{            System.out.println("田螺线程池方式,异步线程:" + Thread.currentThread().getName());        });    }}//  运行结果:田螺主线程:main田螺线程池方式,异步线程:pool-1-thread-1

3. 使用自定义线程池

使用使用Executors提供线程池,如newFixedThreadPool,虽然简单快捷,但是呢,它的阻塞队列十无界的!

newFixedThreadPool默认使用LinkedBlockingQueue作为任务队列,而LinkedBlockingQueue是一个无界队列(默认容量为Integer.MAX_VALUE)。如果任务提交速度远大于线程池处理速度,队列会不断堆积任务,最终可能导致内存耗尽.

因此,我们一般推荐使用自定义线程池,来开启异步。

public class Test {    public static void main(String[] args) {        // 自定义线程池        ThreadPoolExecutor executor = new ThreadPoolExecutor(                2, // 核心线程数                4, // 最大线程数                60, // 空闲线程存活时间                TimeUnit.SECONDS, // 时间单位                new ArrayBlockingQueue<>(8), // 任务队列                Executors.defaultThreadFactory(), // 线程工厂                new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略        );        System.out.println("田螺主线程:" + Thread.currentThread().getName());        executor.execute(() -> {            try {                Thread.sleep(500); // 模拟耗时操作                System.out.println("田螺自定义线程池开启异步:" + Thread.currentThread().getName());            } catch (InterruptedException e) {                e.printStackTrace();            }        });    }}//输出田螺主线程:main田螺自定义线程池开启异步:pool-1-thread-1

4. 使用Future和Callable

有些小伙伴说,如果我们期望异步编程可以返回结果呢?

那我们可以使用Future和Callable。Future和Callable是Java 5引入的,用于处理异步任务。Callable类似于Runnable,但它可以返回一个结果,并且可以抛出异常。Future表示异步计算的结果。

简单使用demo:

public class Test {    public static void main(String[] args) throws ExecutionException, InterruptedException  {        // 自定义线程池        ThreadPoolExecutor executor = new ThreadPoolExecutor(                2, // 核心线程数                4, // 最大线程数                60, // 空闲线程存活时间                TimeUnit.SECONDS, // 时间单位                new ArrayBlockingQueue<>(8), // 任务队列                Executors.defaultThreadFactory(), // 线程工厂                new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略        );        System.out.println("田螺主线程:" + Thread.currentThread().getName());        Callable<String> task = () -> {            Thread.sleep(1000); // 模拟耗时操作            System.out.println("田螺自定义线程池开启异步:" + Thread.currentThread().getName());            return "Hello, 公众号:捡田螺的小男孩!";        };        Future<String> future = executor.submit(task);        String result = future.get(); // 阻塞直到任务完成        System.out.println("异步结果:"+result);    }}运行结果:田螺主线程:main田螺自定义线程池开启异步:pool-1-thread-1异步结果:Hello, 公众号:捡田螺的小男孩!

5. 使用CompletableFuture

CompletableFuture是Java 8引入的,提供了更强大的异步编程能力,支持链式调用、异常处理、组合多个异步任务等。

简单使用demo:

public class Test {    public static void main(String[] args) throws ExecutionException, InterruptedException  {        // 自定义线程池        ThreadPoolExecutor executor = new ThreadPoolExecutor(                2, // 核心线程数                4, // 最大线程数                60, // 空闲线程存活时间                TimeUnit.SECONDS, // 时间单位                new ArrayBlockingQueue<>(8), // 任务队列                Executors.defaultThreadFactory(), // 线程工厂                new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略        );        System.out.println("田螺主线程:" + Thread.currentThread().getName());        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {            try {                Thread.sleep(1000); // 模拟耗时操作                System.out.println("田螺CompletableFuture开启异步:" + Thread.currentThread().getName());                return "Hello, 公众号:捡田螺的小男孩!";            } catch (InterruptedException e) {                e.printStackTrace();            }            return "Hello, 公众号:捡田螺的小男孩!";        },executor);        future.thenAccept(result -> System.out.println("异步结果:" + result));        future.join();    }}

6. 使用ForkJoinPool

有些时候,我们希望开启异步,将一个大任务拆分成多个小任务(Fork),然后将这些小任务的结果合并(Join)。这时候,我们就可以使用ForkJoinPool啦~。

ForkJoinPool 是 Java 7 引入的一个线程池实现,专门用于处理分治任务。

  • 它的特点就是任务拆分(Fork)和结果合并(Join),以及工作窃取(Work-Stealing)。

  • ForkJoinPool 特别适合处理递归任务或可以分解的并行任务。

简单使用demo:

public class Test {    public static void main(String[] args) {        ForkJoinPool pool = new ForkJoinPool(); // 创建 ForkJoinPool        int result = pool.invoke(new SumTask(1, 100)); // 提交任务并获取结果        System.out.println("1 到 100 的和为: " + result);    }    static class SumTask extends RecursiveTask<Integer> {        private final int start;        private final int end;        SumTask(int start, int end) {            this.start = start;            this.end = end;        }        @Override        protected Integer compute() {            if (end - start <= 10) { // 直接计算小任务                int sum = 0;                for (int i = start; i <= end; i++) sum += i;                return sum;            } else { // 拆分任务                int mid = (start + end) / 2;                SumTask left = new SumTask(start, mid);                SumTask right = new SumTask(mid + 1, end);                left.fork(); // 异步执行左任务                return right.compute() + left.join(); // 等待左任务完成并合并结果            }        }    }}

7. Spring的@Async异步

Spring 提供了 @Async 注解来实现异步方法调用,非常方便。使用 @Async 可以让方法在单独的线程中执行,而不会阻塞主线程。

Spring @Async 的使用步骤其实很简单:

  • 启用异步支持:在 Spring Boot项目中,需要在配置类或主应用类上添加 @EnableAsync 注解。

  • 标记异步方法:在需要异步执行的方法上添加 @Async 注解。

  • 配置线程池:默认情况下,Spring 使用一个简单的线程池。如果需要自定义线程池,可以通过配置实现。

启用异步支持:

@SpringBootApplication@EnableAsync // 启用异步支持public class AsyncDemoApplication {    public static void main(String[] args) {        SpringApplication.run(AsyncDemoApplication.class, args);    }}

异步服务类

@Servicepublic class TianLuoAsyncService {    @Async // 标记为异步方法    public void asyncTianLuoTask() {        try {            Thread.sleep(2000); // 模拟耗时操作            System.out.println("异步任务完成,线程: " + Thread.currentThread().getName());        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

默认情况下,Spring 使用一个简单的线程池(SimpleAsyncTaskExecutor),每次调用都会创建一个新线程。因此,在使用Spring的@Async进行异步时,推荐使用自定义线程池。

如下:

@Configurationpublic class AsyncConfig {    @Bean(name = "taskExecutor")    public Executor taskExecutor() {        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();        executor.setCorePoolSize(10); // 核心线程数        executor.setMaxPoolSize(20);  // 最大线程数        executor.setQueueCapacity(50); // 队列容量        executor.setThreadNamePrefix("AsyncThread-"); // 线程名前缀        executor.initialize();        return executor;    }}

然后,在 @Async 注解中指定线程池的名称:

@Async("taskExecutor") // 指定使用自定义的线程池public void asyncTianLuoTask() {    // 方法逻辑}

8. MQ实现异步

我们在提到MQ的时候,经常提到,它有异步处理、解耦、流量削锋。是的,MQ经常用来实现异步编程。

简要代码如下:先保存用户信息,然后发送注册成功的MQ消息

  // 用户注册方法  public void registerUser(String username, String email, String phoneNumber) {      // 保存用户信息(简化版)      userService.add(buildUser(username,email,phoneNumber))      // 发送消息      String registrationMessage = "User " + username + " has registered successfully.";      // 发送消息到队列      rabbitTemplate.convertAndSend("registrationQueue", registrationMessage);  }

消费者从队列中读取消息并发送短信或邮件:

@Servicepublic class NotificationService {    // 监听消息队列中的消息并发送短信/邮件    @RabbitListener(queues = "registrationQueue")    public void handleRegistrationNotification(String message) {        // 这里可以进行短信或邮件的发送操作        System.out.println("Sending registration notification: " + message);        // 假设这里是发送短信的操作        sendSms(message);        // 也可以做其他通知(比如发邮件等)        sendEmail(message);    }  }

9.使用Hutool工具库的ThreadUtil

可以使用的是类似 Hutool 工具库中的 ThreadUtil,它提供了丰富的线程池管理和异步任务调度功能。

先引入依赖:

<dependency>    <groupId>cn.hutool</groupId>    <artifactId>hutool-all</artifactId>    <version>5.8.11</version> <!-- 请使用最新版本 --></dependency>

最简单的,可以直接使用ThreadUtil.execute执行异步任务

public class Test {    public static void main(String[] args) {        System.out.println("田螺主线程");        ThreadUtil.execAsync(                () -> {                    System.out.println("田螺异步测试:" + Thread.currentThread().getName());                }        );    }}//输出田螺主线程田螺异步测试:pool-1-thread-1

图片

  推荐阅读:

  • 2025 年 03 月编程语言排行榜|老古董语言强势回归,原因是相关开发人员退休了~

  • 不要在循环await啦,异步操作的6个最佳实践!

  • 彻底理解异步编程

  • 聊聊异步编程的 7 种实现方式

  • 理想汽车开出了满意的薪资!

  • (0)
    wd123_cnwd123_cn
    上一篇 2025年3月21日 上午10:30
    下一篇 2025年3月21日 上午10:31

    相关文章

    • 最新:杜特尔特将被强行送往海牙

      来源 | 中国新闻社、环球网、参考消息 ‍当地时间3月11日晚,菲律宾副总统莎拉·杜特尔特在社交媒体发表声明证实,在她写这份声明时,“他(前总统杜特尔特)正在被强行送往海牙”。荷兰海牙是国际刑事法院总部所在地。 莎拉在声明中表示,政府将一名菲律宾公民——甚至是一位前总统——交给外国势力,是对菲律宾主权的公然冒犯。 杜特尔特:“我犯了什么罪…

      2025年3月12日
    • 今日最佳:从相杀到相爱。

      来源微博:@霓虹商业波浪趟水交流汀 这是真开车。

      2025年3月21日
    • 西班牙太阳能项目意外揭晓铜器时代堡垒及罗马士兵神秘死亡

      自2021年西班牙大规模太阳能发电厂项目意外发现铜器时代遗址以来,考古学家们一直在该地点挖掘新的发现。最新的一项重大发现是:一座山顶堡垒的详细情况,该堡垒曾由三道同心墙保护。 铜器时代的堡垒 2021年,位于西班牙阿尔门德拉莱霍的太阳能发电厂项目在施工过程中意外发现了铜器时代的遗址。自那以后,考古学家们一直在该地点进行挖掘工作。施工于2021年11月开始,很…

      2025年3月8日
    • 更年期症状影响职场女性,专家呼吁企业提供支持与理解

      纽约(美联社)——Crystal Burke花了五年时间才找到困扰她的症状的名字。心悸、严重失眠、难以决策、处理数据时感到困惑,这些症状让她的工作和生活都受到了影响。直到她看到一则关于含雌激素面霜的广告,她才意识到自己可能正在经历更年期。 更年期的多样症状 Burke年仅38岁,她曾以为自己还太年轻,不会经历更年期。然而,更年期不仅仅是关于50多岁女性潮热的…

      2025年3月7日
    • 企鹅也懂得悲伤?研究揭示它们的情感世界

      企鹅是高度智慧和情感丰富的动物。它们能够识别彼此,甚至能认出自己的伴侣。一些企鹅甚至与人类看护者建立了深厚的情感联系。那么,这是否意味着企鹅也能感受到人类的情感,比如悲伤?让我们一探究竟。 企鹅会哀悼吗? 不幸的是,企鹅对悲伤并不陌生。2014年,BBC记录了一只帝企鹅母亲因失去幼崽而明显悲伤的情景。在视频中,这位母亲试图温暖她的宝宝,同时发出悲伤的声音。片…

      2025年3月6日
    • 2025年复古家具趋势:设计师青睐1920至1990年代经典设计

      根据1stDibs发布的2025年度趋势调查,复古家具和装饰品成为设计师们的首选。2024年,81%的设计师选择了上世纪20年代至90年代的复古单品,预计2025年这一需求将持续增长。 调查显示,人们对中世纪现代风格的兴趣逐渐下降,仅有7%的设计师计划在2025年寻找此类设计。相反,1920至1930年代的装饰艺术(Art Deco)和包豪斯风格正在崛起,定…

      2025年3月12日