天天看點

Java - 線程同步方式

概念

同步異步

消息通信機制

  • 同步:發出調用之後,在沒有得到結果之前,該調用不傳回。調用者主動等待調用結果。
  • 異步:發出調用之後,該調用直接傳回,無結果。被調用者通過狀态、通知進行回報,或者通過回調函數處理這個調用。

阻塞非阻塞

調用者等待調用傳回值時的狀态

  • 阻塞:調用結果傳回之前,目前線程[ 調用者]會被挂起
  • 非阻塞:不能立刻得到結果之前,調用者線程不會被阻塞

鎖機制的意義

Java 允許多線程并發控制,當多個線程同時操作(增删改查)一個被共享的資源變量時,可能導緻資料出現不正确的結果。加入鎖保證了變量的唯一性,準确性。

Java 中的鎖

公平鎖、非公平鎖

公平鎖要求多個等待鎖的線程,必須按照申請鎖的順序獲得鎖,而非公平鎖允許搶占鎖。公平鎖的優點是穩定,等待鎖的線程不會餓死;非公平鎖的優點是性能優,因為某些線程可以避開Blocking,Runnable 之間的狀态轉換。

自旋鎖

線程在進行狀态轉換(作業系統層面)過程要耗費很多時間,并且很多時候共享資料的鎖定狀态隻會持續很短一段時間,為了節省這段很短的時間去挂起、恢複現場并不值得。如果機器上有兩個以上的處理器,可以讓兩個線程并行執行,我們可以後面的線程“稍等片刻”,讓他執行一個“自旋”。自旋等待不能代替阻塞。自旋占用處理器時間,如果等待時間長,還是要讓線程阻塞。自旋隻是在輕量級鎖中使用,在重量級鎖中,線程不能使用自旋。

鎖消除

鎖消除是虛拟機JIT在運作時,檢測一些被同步的代碼,不可能存在共享資料競争的情況,鎖将進行消除的操作。

鎖粗化

鎖範圍擴充被稱之為鎖粗化。

可重入鎖

可重入鎖,也叫遞歸鎖。線程A 持有一個鎖,線程B 不持有鎖,B 通路被這個鎖保護的代碼就會被阻塞,而A 則可以利用其持有的這個鎖多次通路這段代碼。Java 内置鎖(synchronize)和Lock(ReentrantLock)都是可重入的。

類鎖和對象鎖

public class LockStrategy
{
    public Object object1 = new Object();
    // 類鎖 
    public static synchronized void method1(){}
    public void method2(){
        synchronized(LockStrategy.class){}
    }
    // 對象鎖
    public synchronized void method4(){}
    public void method5()
    {
        synchronized(this){}
    }
    public void method6()
    {
        synchronized(object1){}
    }
}      

例題,有一個類這樣定義:

public class SynchronizedTest
{
    public synchronized void method1(){}
    public synchronized void method2(){}
    public static synchronized void method3(){}
    public static synchronized void method4(){}
}      

那麼,有SynchronizedTest的兩個執行個體a和b,對于一下的幾個選項有哪些能被一個以上的線程同時通路呢?

A. a.method1() vs. a.method2()

B. a.method1() vs. b.method1() // a,b,對象鎖

C. a.method3() vs. b.method4()

D. a.method3() vs. b.method3()

E. a.method1() vs. a.method3() //對象鎖,類鎖

答案是什麼呢?BE

偏向鎖、輕量級鎖和重量級鎖

Java 對象記憶體布局中,對象頭的Mark Word:

Java - 線程同步方式

整個 synchronized 鎖流程如下:

  1. 檢測Mark Word裡面是不是目前線程的ID,如果是,表示目前線程處于偏向鎖(偏向于第一個獲得它的線程)
  2. 如果不是,則使用CAS将目前線程的ID替換Mard Word,如果成功則表示目前線程獲得偏向鎖,置偏向标志位1
  3. 如果失敗,則說明發生競争,撤銷偏向鎖,進而更新為輕量級鎖。
  4. 目前線程使用CAS将對象頭的Mark Word替換為鎖記錄指針,如果成功,目前線程獲得鎖
  5. 如果失敗,表示其他線程競争鎖,目前線程便嘗試使用自旋來擷取鎖。
  6. 如果自旋成功則依然處于輕量級狀态。
  7. 如果自旋失敗,則更新為重量級鎖

悲觀鎖、樂觀鎖

悲觀鎖:假定會發生并發沖突,屏蔽一切可能違反資料完整性的操作。

樂觀鎖:假定不會發生并發沖突,隻在送出操作時檢測是否違反資料完整性。(使用版本号或者時間戳來配合實作)

共享鎖,排它鎖

共享鎖:讀操作。事務A 對Data 加鎖之後,事務B 依然可以對Data 加共享鎖(排它鎖不可以),讀操作。

排它鎖:讀、修改操作。事務A 對Data 加鎖之後,事務B 不可以對Data 加任何鎖。

讀寫鎖

資源能夠被多個讀線程通路,或者,隻被一個寫線程通路(不能存在讀線程)。Java當中的讀寫鎖通過ReentrantReadWriteLock實作。

互斥鎖

所謂互斥鎖就是指一次最多隻能有一個線程持有的鎖。synchronized和Lock就是互斥鎖。

線程同步的方式有哪些?

  1. 同步方法:synchronized 關鍵字修飾的方法
  2. 同步代碼塊:synchronized 關鍵字修飾的語句塊
  3. 使用特殊域變量(volatile)實作線程同步
  4. 使用reentrantLock 實作線程同步
  5. 使用局部變量實作線程同步:如果使用ThreadLocal 管理變量,則每一個使用該變量的線程都獲得一個該變量的副本,副本之間互相獨立,這樣每個線程都可以修改自己的變量副本,而不會對其他線程産生影響。