1. 程式人生 > >[高併發Java 一] 前言

[高併發Java 一] 前言

1、關於高併發的幾個重要概念

1.1 同步和非同步

首先這裡說的同步和非同步是指函式/方法呼叫方面。

很明顯,同步呼叫會等待方法的返回,非同步呼叫會瞬間返回,但是非同步呼叫瞬間返回並不代表你的任務就完成了,他會在後臺起個執行緒繼續進行任務。

1.2 併發和並行

併發和並行在外在表象來說,是差不多的。由圖所示,並行則是兩個任務同時進行,而併發呢,則是一會做一個任務一會又切換做另一個任務。所以單個cpu是不能做並行的,只能是併發。

1.3 臨界區

臨界區用來表示一種公共資源或者說是共享資料,可以被多個執行緒使用,但是每一次,只能有一個執行緒使用它,一旦臨界區資源被佔用,其他執行緒要想使用這個資源,就必須等待。

1.4 阻塞和非阻塞

  • 阻塞和非阻塞通常形容多執行緒間的相互影響。比如一個執行緒佔用了臨界區資源,那麼其它所有需要這個資源的執行緒就必須在這個臨界區中進行等待,等待會導致執行緒掛起。這種情況就是阻塞。此時,如果佔用資源的執行緒一直不願意釋放資源,那麼其它所有阻塞在這個臨界區上的執行緒都不能工作。
  • 非阻塞允許多個執行緒同時進入臨界區

所以阻塞的方式,一般效能不會太好。根據一般的統計,如果一個執行緒在作業系統層面被掛起,做了上下文切換了,通常情況需要8W個時間週期來做這個事情。

1.5 死鎖、飢餓、活鎖

所謂死鎖:是指兩個或兩個以上的程序在執行過程中,由於競爭資源或者由於彼此通訊而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的程序稱為死鎖程序。就如同下圖中的車都想前進,卻誰都無法前進。

但是死鎖雖說是不好的現象,但是它是一個靜態的問題,一旦發生死鎖,程序被卡死,cpu佔有率也是0,它不會佔用cpu,它會被調出去。相對來說還是比較好發現和分析的。

與死鎖相對應的是活鎖。

活鎖,指事物1可以使用資源,但它讓其他事物先使用資源;事物2可以使用資源,但它也讓其他事物先使用資源,於是兩者一直謙讓,都無法使用資源。

舉個例子,就如同你在街上遇到個人,剛好他朝著你的反方向走,與你正面碰到,你們都想讓彼此過去。你往左邊移,他也往左邊移,兩人還是無法過去。這時你往右邊移,他也往右邊移,如此迴圈下去。

一個執行緒在取得了一個資源時,發現其他執行緒也想到這個資源,因為沒有得到所有的資源,為了避免死鎖把自己持有的資源都放棄掉。如果另外一個執行緒也做了同樣的事情,他們需要相同的資源,比如A持有a資源,B持有b資源,放棄了資源以後,A又獲得了b資源,B又獲得了a資源,如此反覆,則發生了活鎖。

活鎖會比死鎖更難發現,因為活鎖是一個動態的過程。

飢餓是指某一個或者多個執行緒因為種種原因無法獲得所需要的資源,導致一直無法執行。

1.6 併發級別

併發級別:阻塞和非阻塞(非阻塞分為無障礙、無鎖、無等待)

1.6.1 阻塞

當一個執行緒進入臨界區後,其他執行緒必須等待

1.6.2 無障礙

  • 無障礙是一種最弱的非阻塞排程
  • 自由出入臨界區
  • 無競爭時,有限步內完成操作
  • 有競爭時,回滾資料

和非阻塞排程相比呢,阻塞排程是一種悲觀的策略,它會認為說一起修改資料是很有可能把資料改壞的。而非阻塞排程呢,是一種樂觀的策略,它認為大家修改資料未必把資料改壞。但是它是一種寬進嚴出的策略,當它發現一個程序在臨界區內發生了資料競爭,產生了衝突,那麼無障礙的排程方式則會回滾這條資料。

在這個無障礙的排程方式當中,所有的執行緒都相當於在拿去一個系統當前的一個快照。他們一直會嘗試拿去的快照是有效的為止。

1.6.3 無鎖

  • 是無障礙的
  • 保證有一個執行緒可以勝出

與無障礙相比,無障礙並不保證有競爭時一定能完成操作,因為如果它發現每次操作都會產生衝突,那它則會不停地嘗試。如果臨界區內的執行緒互相干擾,則會導致所有的執行緒會卡死在臨界區,那麼系統性能則會有很大的影響。

而無鎖增加了一個新的條件,保證每次競爭有一個執行緒可以勝出,則解決了無障礙的問題。至少保證了所有執行緒都順利執行下去。

下面程式碼是Java中典型的無鎖計算程式碼

無鎖在Java中很常見

while (!atomicVar.compareAndSet(localVar, localVar+1)) 
{ 
    localVar = atomicVar.get(); 
}

1.6.4 無等待

  • 無鎖的
  • 要求所有的執行緒都必須在有限步內完成
  • 無飢餓的

首先無等待的前提是無鎖的基礎上的,無鎖它只保證了臨界區肯定有進也有出,但是如果進的優先順序都很高,那麼臨界區內的某些優先順序低的執行緒可能發生飢餓,一直出不了臨界區。那麼無等待解決了這個問題,它保證所有的執行緒都必須在有限步內完成,自然是無飢餓的。

無等待是並行的最高級別,它能使這個系統達到最優狀態。

無等待的典型案例:

如果只有讀執行緒,沒有線執行緒,那麼這個則必然是無等待的。

如果既有讀執行緒又有寫執行緒,而每個寫執行緒之前,都把資料拷貝一份副本,然後修改這個副本,而不是修改原始資料,因為修改副本,則沒有衝突,那麼這個修改的過程也是無等待的。最後需要做同步的只是將寫完的資料覆蓋原始資料。

由於無等待要求比較高,實現起來比較困難,所以無鎖使用得會更加廣泛一些。

2. 有關並行的兩個重要定律

這兩個定律都與加速比有關

2.1 Amdahl定律

定義了序列系統並行化後的加速比的計算公式和理論上限

加速比定義:加速比=優化前系統耗時/優化後系統耗時

舉個例子:

加速比=優化前系統耗時/優化後系統耗時=500/400=1.25

這個定理表明:增加CPU處理器的數量並不一定能起到有效的作用 提高系統內可並行化的模組比重,合理增加並行處理器數量,才能以最小的投入,得到最大的加速比。

2.2 Gustafson定律

說明處理器個數,序列比例和加速比之間的關係

則加速比=n-F(n-1) //推導過程略

只要有足夠的並行化,那麼加速比