你應該這樣去開發接口:Java多線程並行計算(Google的Guava使用)

所謂的高併發除了在架構上的高屋建瓴,還得需要開發人員在具體業務開發中注重自己的每一行代碼、每一個細節,面子有的同時,更重要的還是要有裏子。

面對性能,我們一定要有自己的工匠精神,不可以對任何一行代碼妥協!

今天和大家分享在業務開發中如何降低接口響應時間的一個小技巧,也是大家日常開發中比較普遍存在的一個問題,即如何提高程序的並行計算能力?

本文主要包含以下內容:

  1. 順序執行很慢
  2. 線程池+Future並行計算
  3. 使用Java8的CompletableFuture
  4. 使用Guava的ListenableFuture

本文包含代碼內容較多,大家可收藏後自己跟着動手驗證一番~

順序執行

很多時候,我們開發一個接口時候,需要調用多個方法,然後將各個方法返回的數據一起組裝返回給前端,比如這樣的:

可以看到我這裏調用了4個方法,每一個方法爲模擬真實耗時,所以都是延遲100ms返回一個字符串:

可想而知,我們這個接口的響應時間一定會超過400ms,多次執行都會在400ms多一點:

耗時:403ms耗時:409ms耗時:406ms

這就是順序執行,也許大家覺得很Low,但是想想自己的代碼很多時候不就是這樣子的麼?

線程池+Future並行計算

順序執行確實很慢,所以我們需要並行執行,即同時調用這四個方法,熟悉Java多線程的都知道,每個方法單獨開啓一個線程異步去執行就好了,等全部執行完了拿到獨立線程執行的結果再組裝起來就可以了。

但是每次調用都需要創建四個線程,線程的創建和銷燬都是需要開銷的,所以我們就有了池化技術。

線程池、數據庫的連接池等都是採用的池化技術:預先初始生成創建好的線程,等需要調用的時候拿來即用,線程完成工作後迴歸空閒狀態,等待下一次任務的到來,這樣就避免了線程頻繁的創建、銷燬,提高了程序的響應性能。

所以我們在做並行計算的時候一定要充分的利用線程池的相關技術,關於線程池的技術在我的另外一篇文章單獨講到,不瞭解的同學可以初步瞭解一下,面試也是必會題之一:

Java線程池基礎掃盲

下面我們直接上代碼:

線程池+Future

多運行幾次,看輸出響應時間:

耗時:108ms耗時:105ms耗時:105ms

效果是不是很明顯?

直接相當於一個方法的調用耗時,實際開發中如果你的一個接口經過壓測耗時在100ms左右(大多數正規公司對接口性能都會要求不超過100ms),那麼再通過線程池+Future並行計算的方式,並可以瞬間將你的接口性能提高上去,再也不用擔心壓測不過了。

有時候測試同學告訴你接口壓測不過是不是覺得很沒面子?那是對你職業水平很大的否定~

Java8的CompletableFuture

Future是java.util.concurrent併發包中的接口類,用來表示一個線程異步執行後的結果,有如下核心方法:

  • Future.get():阻塞調用線程,直到計算結果返回
  • Future.isDone():判斷線程是否執行完畢
  • Future.cancel():取消當前線程的執行

我們可以知道的是,Future.get()是阻塞調用的,要想拿到線程執行的結果,必須是Future.get()阻塞或者while(Future.isDone())輪詢方式調用。這種方式叫“主動拉(pull)”,現在都流行響應式編程,即“主動推(push)”的方式,當線程執行完了,你告訴我就好了。

Java8設計了CompletableFuture這樣的一個類,我們先來看看如何用CompletableFuture來開發之前的代碼:

CompletableFuture並行計算

這裏可以看到實現方式和Future並沒有什麼不同,但是CompletableFuture提供了很多方便的方法,比如代碼中的allOf,thenApplyAsync,可以將多個CompletableFuture組合成一個CompletableFuture,最後調用join方法阻塞拿到結果。多次調用該接口耗時如下:

耗時:110ms耗時:108ms耗時:105ms

CompletableFuture類中有很多的方法(50+)可以供大家使用,不像Future只要那麼幾個方法可以使用,這也是Java自有庫對Future的一個增強。

這裏只是簡單展示了CompletableFuture的一種用法,實際開發中大家需要根據不同的場景去選擇使用不同的方法,這裏對API不做具體介紹了。

Guava的ListenableFuture

總是有一些牛逼的公司牛逼的人出一些牛逼的開源組件要比官方自帶的工具類要好得多,同樣,谷歌開源的Guava中的ListenableFuture接口對java自帶的Future接口做了進一步拓展,並且提供了靜態工具類Futures。

針對上面的代碼,我們看如何使用ListenableFuture來實現(與之前不同的是,Guava中需要對線程池再進行一次包裝):

執行三次請求耗時:

耗時:103ms耗時:101ms耗時:103ms

最後

以上就是如何讓自己的接口並行計算起來的三種實現方式,屬於日常開發中比較常用的一個小技巧,這裏沒有過多說明這三種方式的具體區別,實際上還需要大家不斷的在開發中去使用,查閱更多相關源碼和資料,只有等你真正用起來的時候,你纔能有所體會!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章