Java后端實(shí)現(xiàn)異步編程的9種方式總結(jié)
1. 使用Thread和Runnable
Thread和Runnable是最基本的異步編程方式,直接使用Thread和Runnable來(lái)創(chuàng)建和管理線程。
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("田螺異步線程測(cè)試:"+Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } }); thread.start(); } }
但是呢,日常工作中,不推薦直接使用Thread和Runnable,因?yàn)樗羞@些缺點(diǎn):
- 資源消耗大:每次創(chuàng)建新線程會(huì)消耗系統(tǒng)資源,頻繁創(chuàng)建和銷毀線程會(huì)導(dǎo)致性能下降。
- 難以管理:手動(dòng)管理線程的生命周期、異常處理、任務(wù)調(diào)度等非常復(fù)雜。
- 缺乏擴(kuò)展性:無(wú)法輕松控制并發(fā)線程的數(shù)量,容易導(dǎo)致系統(tǒng)資源耗盡。
- 線程復(fù)用問(wèn)題:每次任務(wù)都創(chuàng)建新線程,無(wú)法復(fù)用已有的線程,效率低下。
2.使用Executors提供線程池
針對(duì)Thread和Runnable的缺點(diǎn),我們可以使用線程池呀,線程池主要有這些優(yōu)點(diǎn):
- 它幫我們管理線程,避免增加創(chuàng)建線程和銷毀線程的資源損耗。因?yàn)榫€程其實(shí)也是一個(gè)對(duì)象,創(chuàng)建一個(gè)對(duì)象,需要經(jīng)過(guò)類加載過(guò)程,銷毀一個(gè)對(duì)象,需要走GC垃圾回收流程,都是需要資源開(kāi)銷的。
- 提高響應(yīng)速度。 如果任務(wù)到達(dá)了,相對(duì)于從線程池拿線程,重新去創(chuàng)建一條線程執(zhí)行,速度肯定慢很多。
- 重復(fù)利用。 線程用完,再放回池子,可以達(dá)到重復(fù)利用的效果,節(jié)省資源。
有些小伙伴說(shuō),我們可以直接使用Executors提供的線程池呀,非常快捷方便,比如:
- Executors.newFixedThreadPool - Executors.newCachedThreadPool
簡(jiǎn)單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()); }); } } // 運(yùn)行結(jié)果: //田螺主線程:main //田螺線程池方式,異步線程:pool-1-thread-1
3. 使用自定義線程池
使用使用Executors提供線程池,如newFixedThreadPool,雖然簡(jiǎn)單快捷,但是呢,它的阻塞隊(duì)列十無(wú)界的!
newFixedThreadPool默認(rèn)使用LinkedBlockingQueue作為任務(wù)隊(duì)列,而LinkedBlockingQueue是一個(gè)無(wú)界隊(duì)列(默認(rèn)容量為Integer.MAX_VALUE)。如果任務(wù)提交速度遠(yuǎn)大于線程池處理速度,隊(duì)列會(huì)不斷堆積任務(wù),最終可能導(dǎo)致內(nèi)存耗盡.
因此,我們一般推薦使用自定義線程池,來(lái)開(kāi)啟異步。
public class Test { public static void main(String[] args) { // 自定義線程池 ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, // 核心線程數(shù) 4, // 最大線程數(shù) 60, // 空閑線程存活時(shí)間 TimeUnit.SECONDS, // 時(shí)間單位 new ArrayBlockingQueue<>(8), // 任務(wù)隊(duì)列 Executors.defaultThreadFactory(), // 線程工廠 new ThreadPoolExecutor.CallerRunsPolicy() // 拒絕策略 ); System.out.println("田螺主線程:" + Thread.currentThread().getName()); executor.execute(() -> { try { Thread.sleep(500); // 模擬耗時(shí)操作 System.out.println("田螺自定義線程池開(kāi)啟異步:" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } }); } } //輸出 //田螺主線程:main //田螺自定義線程池開(kāi)啟異步:pool-1-thread-1
4. 使用Future和Callable
有些小伙伴說(shuō),如果我們期望異步編程可以返回結(jié)果呢?
那我們可以使用Future和Callable。Future和Callable是Java 5引入的,用于處理異步任務(wù)。Callable類似于Runnable,但它可以返回一個(gè)結(jié)果,并且可以拋出異常。Future表示異步計(jì)算的結(jié)果。
簡(jiǎn)單使用demo:
public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { // 自定義線程池 ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, // 核心線程數(shù) 4, // 最大線程數(shù) 60, // 空閑線程存活時(shí)間 TimeUnit.SECONDS, // 時(shí)間單位 new ArrayBlockingQueue<>(8), // 任務(wù)隊(duì)列 Executors.defaultThreadFactory(), // 線程工廠 new ThreadPoolExecutor.CallerRunsPolicy() // 拒絕策略 ); System.out.println("田螺主線程:" + Thread.currentThread().getName()); Callable<String> task = () -> { Thread.sleep(1000); // 模擬耗時(shí)操作 System.out.println("田螺自定義線程池開(kāi)啟異步:" + Thread.currentThread().getName()); return "Hello, 公眾號(hào):撿田螺的小男孩!"; }; Future<String> future = executor.submit(task); String result = future.get(); // 阻塞直到任務(wù)完成 System.out.println("異步結(jié)果:"+result); } } //運(yùn)行結(jié)果: //田螺主線程:main //田螺自定義線程池開(kāi)啟異步:pool-1-thread-1 //異步結(jié)果:Hello, 公眾號(hào):撿田螺的小男孩!
5. 使用CompletableFuture
CompletableFuture是Java 8引入的,提供了更強(qiáng)大的異步編程能力,支持鏈?zhǔn)秸{(diào)用、異常處理、組合多個(gè)異步任務(wù)等。
簡(jiǎn)單使用demo:
public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { // 自定義線程池 ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, // 核心線程數(shù) 4, // 最大線程數(shù) 60, // 空閑線程存活時(shí)間 TimeUnit.SECONDS, // 時(shí)間單位 new ArrayBlockingQueue<>(8), // 任務(wù)隊(duì)列 Executors.defaultThreadFactory(), // 線程工廠 new ThreadPoolExecutor.CallerRunsPolicy() // 拒絕策略 ); System.out.println("田螺主線程:" + Thread.currentThread().getName()); CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(1000); // 模擬耗時(shí)操作 System.out.println("田螺CompletableFuture開(kāi)啟異步:" + Thread.currentThread().getName()); return "Hello, 公眾號(hào):撿田螺的小男孩!"; } catch (InterruptedException e) { e.printStackTrace(); } return "Hello, 公眾號(hào):撿田螺的小男孩!"; },executor); future.thenAccept(result -> System.out.println("異步結(jié)果:" + result)); future.join(); } }
6. 使用ForkJoinPool
有些時(shí)候,我們希望開(kāi)啟異步,將一個(gè)大任務(wù)拆分成多個(gè)小任務(wù)(Fork),然后將這些小任務(wù)的結(jié)果合并(Join)。這時(shí)候,我們就可以使用ForkJoinPool啦~。
ForkJoinPool 是 Java 7 引入的一個(gè)線程池實(shí)現(xiàn),專門(mén)用于處理分治任務(wù)。
- 它的特點(diǎn)就是任務(wù)拆分(Fork)和結(jié)果合并(Join),以及工作竊?。╓ork-Stealing)。
- ForkJoinPool 特別適合處理遞歸任務(wù)或可以分解的并行任務(wù)。
簡(jiǎn)單使用demo:
public class Test { public static void main(String[] args) { ForkJoinPool pool = new ForkJoinPool(); // 創(chuàng)建 ForkJoinPool int result = pool.invoke(new SumTask(1, 100)); // 提交任務(wù)并獲取結(jié)果 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) { // 直接計(jì)算小任務(wù) int sum = 0; for (int i = start; i <= end; i++) sum += i; return sum; } else { // 拆分任務(wù) int mid = (start + end) / 2; SumTask left = new SumTask(start, mid); SumTask right = new SumTask(mid + 1, end); left.fork(); // 異步執(zhí)行左任務(wù) return right.compute() + left.join(); // 等待左任務(wù)完成并合并結(jié)果 } } } }
7. Spring的@Async異步
Spring 提供了 @Async 注解來(lái)實(shí)現(xiàn)異步方法調(diào)用,非常方便。使用 @Async 可以讓方法在單獨(dú)的線程中執(zhí)行,而不會(huì)阻塞主線程。
Spring @Async 的使用步驟其實(shí)很簡(jiǎn)單:
- 啟用異步支持:在 Spring Boot項(xiàng)目中,需要在配置類或主應(yīng)用類上添加 @EnableAsync 注解。
- 標(biāo)記異步方法:在需要異步執(zhí)行的方法上添加 @Async 注解。
- 配置線程池:默認(rèn)情況下,Spring 使用一個(gè)簡(jiǎn)單的線程池。如果需要自定義線程池,可以通過(guò)配置實(shí)現(xiàn)。
啟用異步支持:
@SpringBootApplication @EnableAsync // 啟用異步支持 public class AsyncDemoApplication { public static void main(String[] args) { SpringApplication.run(AsyncDemoApplication.class, args); } }
異步服務(wù)類
@Service public class TianLuoAsyncService { @Async // 標(biāo)記為異步方法 public void asyncTianLuoTask() { try { Thread.sleep(2000); // 模擬耗時(shí)操作 System.out.println("異步任務(wù)完成,線程: " + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } }
默認(rèn)情況下,Spring 使用一個(gè)簡(jiǎn)單的線程池(SimpleAsyncTaskExecutor
),每次調(diào)用都會(huì)創(chuàng)建一個(gè)新線程。因此,在使用Spring的@Async進(jìn)行異步時(shí),推薦使用自定義線程池。
如下:
@Configuration public class AsyncConfig { @Bean(name = "taskExecutor") public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); // 核心線程數(shù) executor.setMaxPoolSize(20); // 最大線程數(shù) executor.setQueueCapacity(50); // 隊(duì)列容量 executor.setThreadNamePrefix("AsyncThread-"); // 線程名前綴 executor.initialize(); return executor; } }
然后,在 @Async 注解中指定線程池的名稱:
@Async("taskExecutor") // 指定使用自定義的線程池 public void asyncTianLuoTask() { // 方法邏輯 }
8. MQ實(shí)現(xiàn)異步
我們?cè)谔岬組Q的時(shí)候,經(jīng)常提到,它有異步處理、解耦、流量削鋒。 是的,MQ經(jīng)常用來(lái)實(shí)現(xiàn)異步編程。
簡(jiǎn)要代碼如下:先保存用戶信息,然后發(fā)送注冊(cè)成功的MQ消息
// 用戶注冊(cè)方法 public void registerUser(String username, String email, String phoneNumber) { // 保存用戶信息(簡(jiǎn)化版) userService.add(buildUser(username,email,phoneNumber)) // 發(fā)送消息 String registrationMessage = "User " + username + " has registered successfully."; // 發(fā)送消息到隊(duì)列 rabbitTemplate.convertAndSend("registrationQueue", registrationMessage); }
消費(fèi)者從隊(duì)列中讀取消息并發(fā)送短信或郵件:
@Service public class NotificationService { // 監(jiān)聽(tīng)消息隊(duì)列中的消息并發(fā)送短信/郵件 @RabbitListener(queues = "registrationQueue") public void handleRegistrationNotification(String message) { // 這里可以進(jìn)行短信或郵件的發(fā)送操作 System.out.println("Sending registration notification: " + message); // 假設(shè)這里是發(fā)送短信的操作 sendSms(message); // 也可以做其他通知(比如發(fā)郵件等) sendEmail(message); } }
9.使用Hutool工具庫(kù)的ThreadUtil
可以使用的是類似 Hutool 工具庫(kù)中的 ThreadUtil,它提供了豐富的線程池管理和異步任務(wù)調(diào)度功能。
先引入依賴:
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.11</version> <!-- 請(qǐng)使用最新版本 --> </dependency>
最簡(jiǎn)單的,可以直接使用ThreadUtil.execute
執(zhí)行異步任務(wù)
public class Test { public static void main(String[] args) { System.out.println("田螺主線程"); ThreadUtil.execAsync( () -> { System.out.println("田螺異步測(cè)試:" + Thread.currentThread().getName()); } ); } } //輸出 //田螺主線程 //田螺異步測(cè)試:pool-1-thread-1
以上就是Java后端實(shí)現(xiàn)異步編程的9種方式總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于Java異步編程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Springboot項(xiàng)目快速實(shí)現(xiàn)過(guò)濾器功能
上篇文章已經(jīng)給大家介紹了Springboot項(xiàng)目如何快速實(shí)現(xiàn)Aop功能,這篇文章給大家介紹Springboot項(xiàng)目如何快速實(shí)現(xiàn)過(guò)濾器功能,感興趣的小伙伴可以參考閱讀2023-03-03java中struts2實(shí)現(xiàn)文件上傳下載功能
這篇文章主要介紹了java中struts2實(shí)現(xiàn)文件上傳下載功能的方法,以實(shí)例形式分析了struts2文件上傳下載功能的實(shí)現(xiàn)技巧與相關(guān)問(wèn)題,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2016-05-05Spring的Bean注入解析結(jié)果BeanDefinition詳解
這篇文章主要介紹了Spring的Bean注入解析結(jié)果BeanDefinition詳解,BeanDefinition描述了一個(gè)bean實(shí)例,擁有屬性值、構(gòu)造參數(shù)值和具體實(shí)現(xiàn)的其他信息,其是一個(gè)bean的元數(shù)據(jù),xml中配置的bean元素會(huì)被解析成BeanDefinition對(duì)象,需要的朋友可以參考下2023-12-12Java SimpleDateFormat中英文時(shí)間格式化轉(zhuǎn)換詳解
這篇文章主要為大家詳細(xì)介紹了Java SimpleDateFormat中英文時(shí)間格式化轉(zhuǎn)換,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12Java使用openOffice對(duì)于word的轉(zhuǎn)換及遇到的問(wèn)題解決
開(kāi)發(fā)過(guò)程中經(jīng)常會(huì)使用java將office系列文檔轉(zhuǎn)換為PDF, 一般都使用微軟提供的openoffice+jodconverter 實(shí)現(xiàn)轉(zhuǎn)換文檔,下面這篇文章主要給大家介紹了關(guān)于Java通過(guò)openOffice對(duì)于word的轉(zhuǎn)換及遇到問(wèn)題的解決方法,需要的朋友可以參考下2018-09-09springboot實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼的示例代碼
項(xiàng)目里面有用到用戶手機(jī)號(hào)注冊(cè)發(fā)短信功能,本文主要介紹了springboot實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09SpringBoot自定義啟動(dòng)界面的實(shí)現(xiàn)代碼
實(shí)現(xiàn)自定義啟動(dòng)動(dòng)畫(huà)是一項(xiàng)有趣的任務(wù),雖然Spring Boot本身不提供內(nèi)置的動(dòng)畫(huà)功能,但可以通過(guò)一些技巧來(lái)實(shí)現(xiàn),本文主要以Demo的形式展示,再者下面的Demo都可以聯(lián)合使用,需要的朋友可以參考下2024-07-07idea 在springboot中使用lombok插件的方法
這篇文章主要介紹了idea 在springboot中使用lombok的相關(guān)資料,通過(guò)代碼給大家介紹在pom.xml中引入依賴的方法,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-08-08mybatis-plus的批量新增/批量更新以及問(wèn)題
這篇文章主要介紹了Mybatis-Plus實(shí)現(xiàn)批量新增與批量更新以及出現(xiàn)的問(wèn)題,文章中有詳細(xì)的代碼示例,感興趣的同學(xué)可以參考一下2023-04-04