前言

  • Java中,有一個常被忽略 但 非常重要的關鍵字Synchronized
  • 今天,我將詳細講解 Java關鍵字Synchronized的所有知識,希望你們會喜歡

目錄

示意圖

1. 定義

Java中的1個關鍵字

2. 作用

保證同一時刻最多隻有1個執行緒執行 被Synchronized修飾的方法 / 程式碼

其他執行緒 必須等待當前執行緒執行完該方法 / 程式碼塊後才能執行該方法 / 程式碼塊

3. 應用場景

保證執行緒安全,解決多執行緒中的併發同步問題(實現的是阻塞型併發),具體場景如下:

  1. 修飾 例項方法 / 程式碼塊時,(同步)保護的是同一個物件方法的呼叫 & 當前例項物件
  2. 修飾 靜態方法 / 程式碼塊時,(同步)保護的是 靜態方法的呼叫 & class 類物件

4. 原理

  1. 依賴 JVM 實現同步
  2. 底層通過一個監視器物件(monitor)完成, wait()notify() 等方法也依賴於 monitor 物件

監視器鎖(monitor)的本質 依賴於 底層作業系統的互斥鎖(Mutex Lock)實現

5. 具體使用

Synchronized 用於 修飾 程式碼塊、類的例項方法 & 靜態方法

5.1 使用規則

示意圖

5.2 鎖的型別 & 等級

  • 由於Synchronized 會修飾 程式碼塊、類的例項方法 & 靜態方法,故分為不同鎖的型別
  • 具體如下

示意圖

  • 之間的區別

示意圖

5.3 使用方式

/**
 * 物件鎖
 */
    public class Test{ 
    // 物件鎖:形式1(方法鎖) 
    public synchronized void Method1(){ 
        System.out.println("我是物件鎖也是方法鎖"); 
        try{ 
            Thread.sleep(500); 
        } catch (InterruptedException e){ 
            e.printStackTrace(); 
        } 
 
    } 
 
    // 物件鎖:形式2(程式碼塊形式) 
    public void Method2(){ 
        synchronized (this){ 
            System.out.println("我是物件鎖"); 
            try{ 
                Thread.sleep(500); 
            } catch (InterruptedException e){ 
                e.printStackTrace(); 
            } 
        } 
 
    } 
 }

/**
 * 方法鎖(即物件鎖中的形式1)
 */
    public synchronized void Method1(){ 
        System.out.println("我是物件鎖也是方法鎖"); 
        try{ 
            Thread.sleep(500); 
        } catch (InterruptedException e){ 
            e.printStackTrace(); 
        } 
 
    } 

/**
 * 類鎖
 */
public class Test{ 
   // 類鎖:形式1 :鎖靜態方法
    public static synchronized void Method1(){ 
        System.out.println("我是類鎖一號"); 
        try{ 
            Thread.sleep(500); 
        } catch (InterruptedException e){ 
            e.printStackTrace(); 
        } 
 
    } 
 
    // 類鎖:形式2 :鎖靜態程式碼塊
    public void Method2(){ 
        synchronized (Test.class){ 
            System.out.println("我是類鎖二號"); 
            try{ 
                Thread.sleep(500); 
            } catch (InterruptedException e){ 
                e.printStackTrace(); 
            } 
 
        } 
 
    } 
}

5.4 特別注意

Synchronized修飾方法時存在缺陷:若修飾1個大的方法,將會大大影響效率

  • 示例
    若使用Synchronized關鍵字修飾 執行緒類的run(),由於run()線上程的整個生命期內一直在執行,因此將導致它對本類任何Synchronized方法的呼叫都永遠不會成功

  • 解決方案
    使用 Synchronized關鍵字宣告程式碼塊

該解決方案靈活性高:可針對任意程式碼塊 & 任意指定上鎖的物件

程式碼如下
  synchronized(syncObject) { 
    // 訪問或修改被鎖保護的共享狀態 
    // 上述方法 必須 獲得物件 syncObject(類例項或類)的鎖
}

6. 特點

示意圖

注:原子性、可見性、有序性的定義

示意圖

7. 其他控制併發 / 執行緒同步方式

7.1 Lock、ReentrantLock

  • 簡介

示意圖

  • 區別

示意圖

7.2 CAS

7.2.1 定義

Compare And Swap,即 比較 並 交換,是一種解決併發操作的樂觀鎖

synchronized鎖住的程式碼塊:同一時刻只能由一個執行緒訪問,屬於悲觀鎖

7.2.2 原理
// CAS的操作引數
記憶體位置(A)
預期原值(B)
預期新值(C)

// 使用CAS解決併發的原理:
// 1. 首先比較A、B,若相等,則更新A中的值為C、返回True;若不相等,則返回false;
// 2. 通過死迴圈,以不斷嘗試嘗試更新的方式實現併發

// 虛擬碼如下
public boolean compareAndSwap(long memoryA, int oldB, int newC){
    if(memoryA.get() == oldB){
        memoryA.set(newC);
        return true;
    }
    return false;
}


7.2.3 優點

資源耗費少:相對於synchronized,省去了掛起執行緒、恢復執行緒的開銷

但,若遲遲得不到更新,死迴圈對CPU資源也是一種浪費

7.2.4 具體實現方式
  • 使用CAS有個“先檢查後執行”的操作
  • 而這種操作在Java中是典型的不安全的操作,所以 CAS在實際中是C++通過呼叫CPU指令實現的
  • 具體過程
// 1. CAS在Java中的體現為Unsafe類
// 2. Unsafe類會通過C++直接獲取到屬性的記憶體地址
// 3. 接下來CAS由C++的Atomic::cmpxchg系列方法實現
7.2.5 典型應用:AtomicInteger

對 i++ 與 i–,通過compareAndSet & 一個死迴圈實現

compareAndSet函式內部 = 通過jni操作CAS指令。直到CAS操作成功跳出迴圈

   private volatile int value; 
    /** 
     * Gets the current value. 
     * 
     * @return the current value 
     */ 
    public final int get() { 
        return value; 
    } 
    /** 
     * Atomically increments by one the current value. 
     * 
     * @return the previous value 
     */ 
    public final int getAndIncrement() { 
        for (;;) { 
            int current = get(); 
            int next = current + 1; 
            if (compareAndSet(current, next)) 
                return current; 
        } 
    } 
 
    /** 
     * Atomically decrements by one the current value. 
     * 
     * @return the previous value 
     */ 
    public final int getAndDecrement() { 
        for (;;) { 
            int current = get(); 
            int next = current - 1; 
            if (compareAndSet(current, next)) 
                return current; 
        } 
    }

8. 總結

  • 本文主要對Java中常被忽略 但 非常重要的關鍵字Synchronized進行講解

  • 下面我將繼續對 Android & Java中的知識進行深入講解 ,有興趣可以繼續關注Carson_Ho的安卓開發筆記

請幫頂 / 評論點贊!因為你的鼓勵是我寫作的最大動力!