Java后端实现异步编程的9种方式总结
目录
- 1. 使用Thread和Runnable
- 2.使用Executors提供线程池
- 3. 使用自定义线程池
- 4. 使用Future和Callable
- 5. 使用CompletableFuture
- 6. 使用ForkJoinPool
- 7. Spring的@Async异步
- 8. MQ实现异步
- 9.使用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垃圾回收流程,都是需要资源开销的。
- 提高响应速度。 如果任务到达了,相对于从线android程池拿线程,重新去创建一条线程执行,速度肯定慢很多。
- 重复利用。 线程用完,再放回池子,可以达到重复利用的效果,节省资源。
有些小伙伴说,我们可以直接使用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 //田螺自定义线程池开启异python步: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(5python0); // 队列容量 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异步编程的资料请关注编程客栈(www.devze.com)其它相关文章!
精彩评论