Java后端实现异步编程的9种方式总结

 更新时间:2025年03月27日 08:23:29   作者:捡田螺的小男孩  
我们日常开发的时候,经常说到异步编程,比如说,在注册接口,我们在用户注册成功时,用异步发送邮件通知用户,那么实现异步编程一共有多少种方式呢,下面小编就来简单讲讲吧

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);
    }
}

异步服务类

@Service
public 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进行异步时,推荐使用自定义线程池。

如下:

@Configuration
public 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);
  }

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

@Service
public 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

以上就是Java后端实现异步编程的9种方式总结的详细内容,更多关于Java异步编程的资料请关注脚本之家其它相关文章!

相关文章

  • @Transactional注解不起作用的原因分析及解决

    @Transactional注解不起作用的原因分析及解决

    这篇文章主要介绍了@Transactional注解不起作用的原因分析及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • java将m3u8格式转成视频文件的方法

    java将m3u8格式转成视频文件的方法

    这篇文章主要介绍了如何java将m3u8格式转成视频文件,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-03-03
  • java计算百分比值的方法

    java计算百分比值的方法

    这篇文章主要介绍了java计算百分比值的方法,涉及java数值运算的技巧,需要的朋友可以参考下
    2015-03-03
  • 关于Java两个浮点型数字加减乘除的问题

    关于Java两个浮点型数字加减乘除的问题

    由于浮点数在计算机中是以二进制表示的,直接进行加减乘除运算会出现精度误差,想要得到精确结果,应使用BigDecimal类进行运算
    2024-10-10
  • springcloud-eureka与gateway简易搭建过程

    springcloud-eureka与gateway简易搭建过程

    文章介绍了如何搭建Eureka服务注册中心和Spring Cloud Gateway网关,对于Eureka,首先创建eureka-server项目,配置依赖和属性,启动后通过控制台查看服务注册状态,对于Spring Cloud Gateway,创建gateway项目,配置依赖和属性,启动后测试路由转发功能,感兴趣的朋友一起看看吧
    2025-12-12
  • javaWeb项目部署到阿里云服务器步骤详解

    javaWeb项目部署到阿里云服务器步骤详解

    本篇文章主要介绍了javaWeb项目部署到阿里云服务器步骤详解,非常具有实用价值,需要的朋友可以参考下
    2017-05-05
  • 常见的排序算法,一篇就够了

    常见的排序算法,一篇就够了

    这篇文章主要介绍了一些常用排序算法整理,插入排序算法、直接插入排序、希尔排序、选择排序、冒泡排序等排序,需要的朋友可以参考下
    2021-07-07
  • Druid连接池配置过程及连接池调优方式

    Druid连接池配置过程及连接池调优方式

    这篇文章主要介绍了Druid连接池配置过程及连接池调优方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-10-10
  • Java对称加密工作模式原理详解

    Java对称加密工作模式原理详解

    这篇文章主要介绍了Java对称加密工作模式原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • java类比C++的STL库详解

    java类比C++的STL库详解

    这篇文章主要介绍了java类比C++的STL库详解,标准模板库,是C++标准库的重要组成部分,中文可译为标准模板库或者泛型库,其包含有大量的模板类和模板函数,STL 是一些容器、算法和其他一些组件的集合,需要的朋友可以参考下
    2023-08-08

最新评论