天天看點

【Java -- 基礎】關鍵字 synchronized

前言

在 Java 中,有一個常被忽略但非常重要的關鍵字 ​

​synchronized​

​ 。

一、關鍵字 synchronized

1. 作用

保證同一時刻最多隻有 1 個線程執行 被 ​

​synchronized​

​ 修飾的方法 / 代碼

2. 應用場景

保證線程安全,解決多線程中的并發同步問題(實作的是阻塞型并發),具體場景如下:

  • 修飾 執行個體方法 / 代碼塊時,(同步)保護的是同一個對象方法的調用 & 目前執行個體對象
  • 修飾 靜态方法 / 代碼塊時,(同步)保護的是 靜态方法的調用 & class 類對象

3. 原理

  • 依賴 JVM 實作同步
  • 底層通過一個螢幕對象(monitor)完成, wait()、notify() 等方法也依賴于 monitor 對象
螢幕鎖(monitor)的本質 依賴于 底層作業系統的互斥鎖(Mutex Lock)實作

4. 使用

​synchronized​

​ 用于 修飾 代碼塊、類的執行個體方法 & 靜态方法

4.1 鎖的類型 & 等級

由于​​

​synchronized​

​​ 會修飾 代碼塊、類的執行個體方法 & 靜态方法,故分為不同鎖的類型。

【Java -- 基礎】關鍵字 synchronized

4.2 使用規則

【Java -- 基礎】關鍵字 synchronized

4.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(); 
            } 
 
        } 
 
    } 
}      

特别注意:

​​

​synchronized​

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

  • 示例

    若使用 ​

    ​synchronized​

    ​ 關鍵字修飾 線程類的 ​

    ​run()​

    ​,由于​

    ​run()​

    ​線上程的整個生命期内一直在運作,是以将導緻它對本類任何​

    ​synchronized​

    ​方法的調用都永遠不會成功
  • 解決方案

    使用 ​

    ​synchronized​

    ​ 關鍵字聲明代碼塊
該解決方案靈活性高:可針對任意代碼塊 & 任意指定上鎖的對象
代碼如下
  synchronized(syncObject) { 
    // 通路或修改被鎖保護的共享狀态 
    // 上述方法 必須 獲得對象 syncObject(類執行個體或類)的鎖
}      

二、CAS

  • 定義

    ​​

    ​Compare And Swap​

    ​,即 比較并交換,是一種解決并發操作的樂觀鎖
synchronized 鎖住的代碼塊:同一時刻隻能由一個線程通路,屬于悲觀鎖
  • 原理
// 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;
}      
  • 優點

    資源耗費少:相對于​​

    ​synchronized​

    ​,省去了挂起線程、恢複線程的開銷
但,若遲遲得不到更新,死循環對CPU資源也是一種浪費
  • 具體實作方式
  • 使用​

    ​CAS​

    ​ 有個“先檢查後執行”的操作
  • 而這種操作在 Java 中是典型的不安全的操作,是以​

    ​CAS​

    ​​ 在實際中是由​

    ​C++​

    ​​ 通過調用​

    ​CPU​

    ​ 指令實作的
  • 具體過程
  1. CAS在Java中的展現為Unsafe類
  2. Unsafe類會通過C++直接擷取到屬性的記憶體位址
  3. 接下來CAS由C++的Atomic::cmpxchg系列方法實作
  • 典型應用:AtomicInteger

    對 i++ 與 i–,通過​​

    ​compareAndSet​

    ​ & 一個死循環實作
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; 
        } 
    }