一場賽跑引發的併發知識

享學特邀做者:老顧


前言

咱們小夥伴們是否是常常須要測試代碼的性能?小夥伴們是否是就會想到jmeter進行壓力測試一下,模擬N個用戶同時執行下,看看響應的時間多少java

今天老顧就用一個經典的比賽案例,來嘗試自行編寫個比賽業務,並隨便介紹一下CyclicBarrier和CountDownLatch區別。 能夠根據比賽業務,能夠抽象出性能測試工具類編程

需求場景

有N個短跑選手進行比賽,但願記錄下來整個比賽的時間安全

一、 第一名選手跑完比賽路程所花時間
二、 最後一名選手跑完比賽路程所花時間
三、 全部選手跑完比賽路程所花時間
四、 每一個選手跑完比賽路程所花時間之和

上面就是業務需求,那咱們如何去實現?bash

分析需求

先上個圖,小夥伴們會更好理解架構

上圖中4個選手在進行賽跑,咱們先看一下賽跑規則:併發

一、每一個 選手互不干擾,在本身的賽道上面進行跑步
二、選手在跑步前都須要活動一下, 作好預備姿式
三、選手是應該在 裁判一聲槍響下,才能開始跑,不能提早跑
四、每一個選手在跑到終點時, 裁判會爲每一個選手記錄成績
五、比賽結束後,大會公佈各個選手的成績,以及排名

設計思路

針對上面的規則,咱們須要轉換成咱們的程序設計:工具

一、 選手間互不干擾,又同時進行賽跑。這個比較簡單,確定用咱們的 Thread線程去解決。
二、記錄時間這個也比較簡單,利用System的時鐘

咱們先到這邊,先上代碼,建立任務(賽跑)性能

RunTask表明選手的跑完比賽的耗時,爲了真實模擬,加了隨機數,表示每一個選手的耗時不同。繼續代碼,直接在main方法中,進行比賽學習

上面是一些基礎變量,記錄耗時。小夥伴要注意要用AtomicLong原子類避免線程安全問題。下面的代碼就是比賽核心邏輯測試

一、建立線程(選手)
二、執行任務(賽跑)
三、記錄成績(耗時)

大會公佈成績

執行比賽

小夥伴看看,是否是明顯不對啊,總耗時盡然爲0,確定有問題。

應該有人發現了,由於咱們是在main方法中執行比賽的,其餘線程單獨執行,主main線程執行完就終止了程序,而不會管其餘線程有沒有結束

這明顯和咱們想要的不同,咱們須要等全部的選手跑完,才能算比賽結束。那應該怎麼優化呢?往下看

CyclicBarrier

咱們這裏引入一個知識點CyclicBarrier循環屏障,CyclicBarrier是一組線程互相等待,只有所有到達屏障點之後才能繼續執行。能夠舉個生活場景

大巴車進入服務區進行休息,大巴車是要 等到全部乘客上車後,才能發車。 並非一我的上車了就能夠發車了。這個是全部乘客都知道的規則, 互相等待全部人上車,才發車。 循環的意思就是大巴車是一直這種規則,可重複利用

咱們比賽的例子正好匹配,不是一個選手到達終點(屏障)就比賽結束,而是要等到全部選手到達終點才能結束比賽

終點優化

根據上面的CyclicBarrier知識點,咱們把代碼優化一下

1、增長CyclicBarrier變量

//定義屏障,爲何要加1?
final CyclicBarrier cb = new CyclicBarrier(nThreads + 1);複製代碼

爲何要加1?由於比賽裁判確定先到終點(即主線程),那也須要等待,因此屏障點須要加1。

注意:這個是根據業務來的,若是設置屏障點,是根據業務邏輯設計的

2、選手跑完到屏障點

在選手跑完後,增長到達屏障點,等待

3、裁判到屏障點

這個代碼是在main主線程的,也就是裁判會先到,設置屏障點

終點優化結束,執行比賽吧

這個成績應該沒有問題,把大賽的成績都正確的顯示出來了。

系統耗時

咱們小夥伴再仔細觀察下,上面的成績:

一、最後一名的耗時3397ms

二、比賽執行完耗時3398ms

相差1ms,固然咱們這裏設計是以毫秒爲單位的,若是以納秒爲單位他們的相差會比1ms少。這個不是關鍵,關鍵是其實最後一名跑完,其實就是比賽結束了。按照道理比賽執行耗時和最後一名的耗時是同樣的哦。

比賽執行屢次,效果都同樣相差1ms。這個是爲何呢?就是由於系統耗時,咱們看看比賽是在何時記時的,是所有選手開跑後才記時的。這邊就會存在偏差,由於系統執行也會耗時

//上面全部選手都已經開跑了
//整個比賽的開始時間
long startTime = System.currentTimeMillis();
//。。。
//整個比賽的結束時間
long endTime = System.currentTimeMillis();複製代碼

就是由於系統執行也是會消耗時間的。固然耗時不大就是幾納秒。小夥伴知道這個點後,會不會發現咱們整個代碼還存在一個問題?

起點問題

咱們一場比賽是要等全部選手準備好後,等待裁判發令後,才能開跑。咱們來看一下咱們的選手開跑代碼

選手是經過for循環建立出來的,並且創新好後,就執行start開跑了。這個是不對的。

小夥伴會說for循環很快的,不要緊吧。

這裏是頗有關係的,建立選手耗時是比較長的,並且循環體也有耗時。咱們看一下以前的系統耗時,就是獲取結束時間也存在系統耗時,況且這裏要分配內存、建立對象等。這樣對其餘選手就不公平了,那怎麼辦?老顧再分享一個併發控制類CountDownLatch

CountDownLatch

CountDownLatch是一個或一組線程等待其餘線程完成各自的工做後再執行。舉個例子:

你們考場考試, 有人提早交卷,但監考老師是不能走的,由於還有人沒有考完,只有等到 全部人交卷了,老師才能走。是否是和CyclicBarrier相似,他們也有不一樣點,自行百度。

到咱們這個案例中,應該要等待全部選手準備好後,才能開跑。

起點優化

增長變量,計數器爲1,這個值是由咱們的設計決定的

//增長CountDownLatch控制類
final CountDownLatch cdl = new CountDownLatch(1);複製代碼

選手預備等待

裁判發令

裁判發令後,全部的選手就會當即開跑,利用CountDownLatch達到了控制線程等待,一塊兒執行。再執行比賽看看,也解決了系統耗時偏差的問題

-----------大會公佈成績-------------
比賽選手數:4
------------------------
全部選手總耗時:6686ms
比賽執行完耗時:1920ms
第一名耗時:1281ms
最後一名耗時:1920ms複製代碼

總結

這篇文章只是個引子,把併發編程的兩個重要的類拋出來,主要介紹應用場景。具體類的用法,小夥伴們能夠網上自行學習。還有CyclicBarrier和CountDownLatch二者有相同點,有些場景能夠替換使用。固然他們也有不一樣點,小夥伴們要注重關注。謝謝!!!

小夥伴是否是會說,那個 性能測試工具類呢?其實上面已經把90%的核心代碼介紹了, 把跑步抽象成外部傳入的任務,在加入 循環執行次數就ok了, 小夥伴能夠自行完善

END

歡迎長按下圖關注公衆號:享學課堂online!

公衆號後臺回覆【java】,獲取精選準備的架構學習資料(視頻+文檔+架構筆記)

相關文章
相關標籤/搜索