實戰Java高併發程序設計(一)走進併發世界

  • 基本概念

    1. 併發(concurrency)和並行(parallelism)
      併發偏重多個任務交替執行,而多個任務之間可能還是串行的。
      並行是真正意義上的“同時執行”。
    2. 臨界區
      臨界區用來表示一種公共資源,或者說是共享數據,可以被多個線程使用。但是每次只能由一個線程使用它。
    3. 阻塞(blocking)和非阻塞(non-blocking)
      阻塞指多個線程因爲需要某個臨界區資源而發生等待。
      非阻塞是指沒有一個線程會妨礙到其他線程執行。
    4. 死鎖(deadlock)、飢餓(starvation)和活鎖(livelock)
      死鎖是最糟糕的一種情況,線程之間需要彼此的資源,但都搶佔資源不釋放,造成阻塞狀態永久維持下去。
      飢餓是指因爲線程優先級太低,高優先級的線程不斷搶佔它需要的資源,導致該線程長久等待。
      活鎖是指兩個線程發現自己的資源不夠之後,都釋放手上的資源,導致資源在兩個線程之間不斷跳動,而沒有一個線程能夠正常執行。
  • 併發級別

    1. 阻塞(blocking)一個線程是阻塞的,那麼其它的線程釋放資源之前,當前線程無法繼續執行。使用synchronized或者重入鎖會使線程這是。
    2. 無飢餓(starvation-free):對於非公平的鎖來說,系統允許高優先級的線程插隊,會造成飢餓;而公平的鎖則不會造成飢餓。
    3. 無障礙(obstruction-free)是一種最弱的非阻塞調度。如果兩個線程無障礙的執行,那麼他們不會因爲臨界區的問題導致一方被掛起。但如果大家一起修改了臨界區,那麼無阻礙線程就會對自己的修改進行回滾。如果臨界區中有嚴重的衝突,會導致所有線程會不斷回滾自己的操作,從而沒有一個線程能夠走出臨界區。
    4. 無鎖(lock-free)的並行是無障礙的,所有的線程都能嘗試對臨界區進行訪問。但不同的是,無鎖的併發保證必然有一個線程能夠在有限步內完成操作離開臨界區。運氣不好的線程還是會飢餓。
    5. 無等待(wait-free)在無鎖的基礎上更進一步進行擴展,它要求所有的線程丟必須在有限步內完成,這樣就不會引起飢餓問題。一種典型的無等待結構是RCU(Read-Copy-Update)。它的基本思想是,對數據的讀可以不加控制,因此所有的讀線程都是無等待的。寫數據時,先取得原數據的副本,然後只修改副本,並在合適的時候寫回。
  • 有關並行的兩個重要定律

    1. Amdahl定律,它定義了串行系統並行化後的加速比的計算公式和理論上限。
      • 加速比 = 優化前系統耗時 / 優化後系統耗時。 加速比越高,表明優化效果越明顯。
      • 使用多核CPU對系統進行優化,優化的效果取決於CPU的數量以及系統中的串行化程序的比重。
      • 加速比 = 1F+1n(1F) ,其中n是處理器個數、F是串行比例
      • 加速比的極限是1F
    2. Gustafson定律,它也試圖說明處理器個數、串行比例和加速比之間的關係。
      • 加速比=nF(n1)
      • 只要不斷增加處理器,就能夠獲得更快的速度。
    3. Amdahl定律與Gustafson定律是否矛盾
      • Amdahl強調:當串行比例一定時,加速比是有上限的,不管你堆疊多少個CPU參與計算,都不能突破這個上限。
      • Gustafson強調:如果可被並行化的代碼所佔比例足夠多,那麼加速比就能隨着CPU的屬性線性增長。(線性增長是否有問題?)
  • JMM

    1. 原子性(atomicity)是指一個操作是不可中斷的。

      • 對於32位系統來說,對long類型數據的讀寫是不具有原子性的,因爲long類型佔64位。
    2. 可見性(visibility)是指當一個線程修改了某一個共享變量時,其他線程能夠立馬知道這個修改。

      • 當CPU1和CPU2上各有一個線程,它們共享變量t,緩存優化的原因,CPU1將t緩存到cache中,此時如果CPU2對該變量進行改動,那麼CPU1無法意識到這個改動。當然還有編譯優化、硬件優化、指令重排等技術也會導致這個現象。
    3. 有序性(ordering),在併發時,程序的執行可能會出現亂序。

      • 有序性的問題的原因是:程序在執行時,可能會進行指令重排。
      • 執行重排的基本前提是,保證串行語義的一致性,但是不保證多線程間的語義一致。
      • 指令重排的意義:在流水線技術中,每次指令中斷後,需要幾個週期才能夠滿載,因此性能損失比較大。爲了提升性能,就要減少指令被中斷的次數。而指令重排就是爲了減少指令被中斷的次數。
    4. Happen-Before原則:指令重排不可違背的原則

      • 程序順序原則:一個線程內保證語義的串行性。
      • volatile規則:volatile變量的寫,先發生於讀,這保證了volatile的可見性。
      • 鎖規則:解鎖必然發生於隨後的加鎖之前。
      • 傳遞性:A先於B,B先於C,那麼A必然先於C。
      • 線程的start()法先於它的每一個動作。
      • 線程的所有操作先於線程的終結。
      • 線程的中斷先於被中斷的線程的代碼。
      • 對象的構造函數的執行、結束先於finalize()方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章