每日一例 | 異步線程優(yōu)化用戶體驗

背景
對于后端開發(fā),接口響應時長是一個很關鍵的點,它不僅體現(xiàn)了你寫的接口的性能,同時也代表著用戶體驗,如果你的內(nèi)容響應時間過長,用戶可能早就把網(wǎng)頁關閉了。
互聯(lián)網(wǎng)行業(yè),有一個用戶體驗原則——2/5/10秒原則。
就是說,在2秒之內(nèi)給客戶響應被用戶認為是“非常有吸引力”的用戶體驗。
在5秒之內(nèi)響應客戶被認為“比較不錯”的用戶體驗,在10秒內(nèi)給用戶響應被認為“糟糕”的用戶體驗。
如果超過10秒還沒有得到響應,那么大多用戶會認為這次請求是失敗的。
所以很多互聯(lián)網(wǎng)企業(yè),接口發(fā)布上線前,都有一個壓測要求:特定并發(fā)量下,接口的響應時間不能大于200ms。
在這樣的要求之下,想要提升接口性能,減少用戶等待時間,將部分非實時、不重要、確定無異常等交易(比如訂單處理、辦理完某個業(yè)務給用戶發(fā)消息等),設計成異步處理的方式是一個不錯的選擇,今天我們就來通過一個簡單的示例演示,來了解下異步交易如何實現(xiàn)。
異步交易演示
假設,我們有這樣的業(yè)務需求:
場景一:我們有一個教務管理系統(tǒng),講師導入創(chuàng)建了一門新的課程,并制定了學員,處理課程創(chuàng)建的操作外,我們還需要在課程創(chuàng)建成功后,給學員發(fā)消息。但是發(fā)消息并非是特別重要的操作,這時候我們就可以通過異步交易來發(fā)送消息。
場景二:我們需要在用戶注冊成功后,給用戶發(fā)送消息(郵件或短信)通知用戶,和場景一類似,發(fā)送消息非必須業(yè)務,為了提升系統(tǒng)響應效率,這時候異步交易是個不錯的選擇。
場景說完了,下來看我們的簡單應用:
我們先創(chuàng)建了一個線程池:
private static ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(5, 10, 1,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(5));
然后,我們在核心交易的最后,通過線程池的submit啟動了一個線程:
threadPoolExecutor.submit(() -> this.sendMessage(messageList));
通過這個線程去執(zhí)行發(fā)消息的操作,這里我們用到了lamubda表達式,上面的代碼等同于:
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
sendMessage(messageList);
}
});
下面是完整代碼:
/**
* @author syske
* @date 2021-05-01 9:34
*/
public class Example {
private static ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(5, 10, 1,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(5));
/**
* 發(fā)送消息
* @param messageList 消息列表
* @return
*/
public AtomicInteger sendMessage(List<String> messageList) {
AtomicInteger successCount = new AtomicInteger();
messageList.forEach(m -> {
System.out.println("發(fā)送消息:" + m);
successCount.getAndIncrement();
});
return successCount;
}
/**
* 核心業(yè)務處理
* @return
*/
public String deal() {
List<String> messageList = new ArrayList<>();
// doSomeThing() 其他業(yè)務處理
System.out.println("開始組裝消息~Start");
for (int i = 0; i < 1000; i++) {
int randomInt1 = new Random().nextInt();
messageList.add("發(fā)送數(shù)字信息:" + randomInt1);
}
System.out.println("組裝消息完成~End");
System.out.println("開始發(fā)送消息~Start");
threadPoolExecutor.submit(() -> this.sendMessage(messageList));
System.out.println("發(fā)送消息完成~End");
return "業(yè)務處理完成";
}
public static void main(String[] args) {
Example example = new Example();
String result = example.deal();
System.out.println(result);
threadPoolExecutor.shutdown();
}
}
執(zhí)行上面的方法,你會發(fā)現(xiàn),核心業(yè)務方法在消息沒發(fā)送就已經(jīng)返回了:
開始組裝消息~Start
組裝消息完成~End
開始發(fā)送消息~Start
發(fā)送消息完成~End
業(yè)務處理完成
發(fā)送消息:發(fā)送數(shù)字信息:-123158837
……
……
發(fā)送消息:發(fā)送數(shù)字信息:-1354635036
這樣我們的異步交易就實現(xiàn)了,是不是很簡單呀
,如果你的異步業(yè)務是有返回值的,那在啟動線程的時候,你可以通過Callable來實現(xiàn),它和Runnable沒有本質(zhì)區(qū)別,只是它是可以有返回值的。
總結(jié)
其實今天的內(nèi)容很簡單,實現(xiàn)過程也很容易,異步交易中真正難的是如何拆分你的業(yè)務,這就需要你自己多思考,多實踐,多總結(jié)了,還是那句話會用這個工具很簡單,但清楚什么時候用這個工具才更重要。好了,各位小伙伴節(jié)日快樂,好好享受假期,但也別忘了學習哦
項目路徑:
https://github.com/Syske/example-everyday
本項目會持續(xù)每日更新,讓我們一起學習,一起進步,遇見更好的自己,加油呀
