java多執行緒的深入理解以及原理解讀

2020-10-13 11:00:18



一. 為什麼需要多執行緒

這個問題是給基礎不紮實的人提的;
原因一首先提一下QPS計算公式(並行數/響應時間(s)),使用多執行緒就是提升並行數,換句話說就是為了提升QPS;那麼多執行緒越多是不是QPS就越高效能就越好呢?當然不是,分子雖然提高了,但是分母也增大了;多執行緒的使用會在CPU上開闢一個時間片,會增加上下文切換的耗時;因此多執行緒也不是越多越好
原因二大部分專案當中如果使用單執行緒,那麼從一個請求進來到響應,只有協定解析和響應後資料處理佔用了CPU(先不考慮計算型服務),那麼請求傳送到服務後,CPU一直處於閒置狀態,等待IO/磁碟處理結束;引入多執行緒後,當程式處理IO時可以要CPU處理其他的事情,等到當前執行緒需要CPU時再切換回來處理;因此多執行緒能充分利用CPU來提升效能;這裡細心的朋友應該還發現了多執行緒的一個特點,多執行緒並不是真正的並行一起執行,而是不同時間片來回切換來執行不同任務,只是cpu處理的效率很快,給人的感覺是同時在執行

二. 如果使用執行緒池,執行緒池的執行緒數怎麼設定

關於執行緒池執行緒數的設定應該很多地方都有介紹,我相信大家只是看了個結果,不知道原理;我這裡就詳細介紹一下.
首先提幾個關鍵詞,cpu核數、IO密集型服務、計算型密集型服務、阻塞係數=阻塞時間/(阻塞時間+計算時間);

阻塞這裡是針對IO來說的,所以阻塞時間就是IO耗時;計算時間級CPU耗時;
然後我再舉一個例子,假如一個服務中,阻塞時間50毫秒,計算時間10毫秒,即cpu有50毫秒的時間處於閒置;是不是在這期間cpu可以處理5個這樣的請求;再加上第一個請求總共就是處理6個請求。

那麼我再提出公式
執行緒數= ncpu/(1-阻塞係數)
然後把上面的50、10帶入進去計算,如果是單核是不是就是6,那麼四核就可以設定大概24個執行緒
上面公式也可以等效成:執行緒數=ncpu*(1+阻塞時間/計算時間);

看到這裡大家應該看懂了怎麼計算執行緒數,可能會提出疑問,為什麼網上看到的計算型密集服務的執行緒數公式=ncpu+1(大家先忽略公式)比IO型服務的設定的執行緒數要偏少很多呢;那是因為之所以叫計算型服務是因為服務本身大部分都是在計算,而計算是影響CPU的時間,所以一個服務進來幾乎都是計算(CPU)耗時,那麼大家根據這個特性把上面的阻塞時間改成0,計算時間改成50,帶入第一個公式,求出來的話1核就是一個執行緒數,ncpu+1又是什麼道理呢?為什麼要+1?帶入現實情況中考慮,計算型的服務不可能全部是計算,總會有一部分IO耗時,這個是毋庸置疑的,然後把引數在改一下,IO耗時10,計算耗時50,那麼這就對應的上了;所以執行緒數= ncpu/(1-阻塞係數)這個公式是通用的;同時也得出另一個結論,阻塞係數越大,執行緒數設定的可以越多,反則設定越少

三. CPU的利用率是怎麼計算的,怎麼防止CPU過高導致程式奔潰

CPU的使用率=CPU計算時間/CPU計算時間+CPU閒置時間;因此如果CPU計算時間100毫米,閒置時間900毫米,那麼利用率就是10%,如果計算時間500毫米,閒置時間0毫米,那麼利用率就是100%(系統是統計單位時間內的值);所以隨著CPU的不斷計算,CPU率也是不斷變化的。
CPU過高的話,首先要定位是什麼導致的,盲目開闢新的執行緒,還是程式計算過於複雜,還是不巧當的迴圈或者死迴圈;盲目開執行緒可以用執行緒池解決,後面這兩種就是優化程式了,如果無法優化了,可以使用sleep(0)、sleep(1)這種來解決;可能有人會問sleep(0)這有什麼意義,他能優化CPU?當然可以,他雖然是讓cpu休眠0毫秒,但是還有一個作用就是會觸發cpu時間片的競爭(重新選舉),所以就不會出現一個時間片獨佔整個cpu,導致cpu瞬間飆高;
既然說到這了我就再提一個問題,sleep() 和yield()有什麼區別?

  1. 執行sleep()後觸發所有時間片一起選舉,每個時間片都可能拿到下一次執行權,yield()指揮把執行權交給優先順序相同或者更高的
  2. 執行緒執行 sleep() 進入阻塞狀態,執行yield() 方法進入就緒狀態
  3. sleep() 宣告丟擲 InterruptedException;yield() 方法沒有宣告丟擲異常
  4. sleep() 有時間阻塞時間引數;yield() 無引數(直接讓出 CPU 的執行權時間由 JVM 控制)

一個個字手敲的,麻煩大家給個贊支援下,謝謝!如有不同意見歡迎評論;

轉載請註明出處,原創不易!