⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 捡田螺的小男孩 「捡田螺的小男孩」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

前言

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

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

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

staticclass SumTask extends RecursiveTask<Integer> {
privatefinalint start;
privatefinalint 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
publicclass 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
文章目录
  1. 1. 前言
  2. 2. 1. 使用Thread和Runnable
  3. 3. 2. 使用Executors提供线程池
  4. 4. 3. 使用自定义线程池
  5. 5. 4. 使用Future和Callable
  6. 6. 5. 使用CompletableFuture
  7. 7. 6. 使用ForkJoinPool
  8. 8. 7. Spring的@Async异步
  9. 9. 8. MQ实现异步
  10. 10. 9.使用Hutool工具库的ThreadUtil