帶你深入理解和解剖 synchronized

1. 是什麼?

synchronized 是 JAVA 關鍵字,用在方法或代碼塊。控制方法或代碼同一時間只有一個線程執行,用來解決多線程同時訪問出現的併發問題。

2. 使用分類(作用域)

對象鎖(代碼塊)

  • 表現形式1:加在方法上
private synchronized boolean sellTicketsWithSyncMethod(int count) {
    if (totalTickets - count < 0) {
        return false;
    } else {
        totalTickets = totalTickets - count;
        return true;
    }
}
  • 表現形式2:加在對象上

鎖多個對象時,相互不干擾

private final Object LOCK = new Object();
private boolean sellTicketsWithSyncObject(int count) {
    synchronized (LOCK) {
        if (totalTickets - count < 0) {
            return false;
        } else {
            totalTickets = totalTickets - count;
            return true;
        }
    }
}
  • 表現形式3:this當前對象上
private boolean sellTicketsWithSyncObject(int count) {
    synchronized (this) {
        if (totalTickets - count < 0) {
            return false;
        } else {
            totalTickets = totalTickets - count;
            return true;
        }
    }
}

類鎖

  • 表現形式1:static
public static synchronized void staticMethod() {
   // todo
}
  • 表現形式2:synchronized(xxx.class)
public Class SyncClass {
    private void method () {
        synchronized (SyncClass.class) {
            //todo
        }
    }
}

對象鎖和類鎖區別?

對象鎖鎖的是一個對象。不同對象下互不干擾。而類只有一個。

3. synchronized 的特徵

  1. 它是一個可重入鎖。
  2. JAVA 語言的關鍵字,由虛擬機字節碼指令實現。(下面深入講解)
    1. 用 monitor 來控制進出
    2. moniterenter 進入
    3. Monitorexit 爲退出
  3. 不可中斷,無法控制鎖的超時時間,可能會造成阻塞。而 ReentranLock 可以,這個後面講解。
  4. synchronized 只有非公平鎖。 公平鎖和非公平鎖的區別
  5. 粒度是對象,鎖的是對象而非變量。

3.1 可重入講解

指的是同一線程的外層函數獲得鎖之後,內層函數可以直接再次獲取該鎖。從而避免死鎖。

演示:

public class SynchronizedReentrantTest {
    int i = 0;
    public synchronized void method1() {
        method2();
    }
 
    private synchronized void method2() {
    }
 
    private synchronized void reentrantSelf() {
        if (i == 0) {
            i++;
            reentrantSelf();
        }
    }
 
    public static void main(String[] args) {
        SynchronizedReentrantTest instance = new SynchronizedReentrantTest();
        
        // 重入其他方法
        instance.method1();
        
        // 自己重入自己
        instance.reentrantSelf();
        
        // 當然也可以重入其他類方法,這裏不演示了。
    }
}

4. 深入理解 synchronized (反編譯來看看)

如何反編譯?

  1. 編譯 javac xxxx.java
  2. 反編譯 javap -c -p -v xxxx.class

以下是截取的部分源碼:

public class SynchronizedMethodDif {
    //...

    // 作用於對象上
    private static final Object LOCK = new Object();
    private static boolean sellTicketsWithSyncObject(int count) {
        synchronized (LOCK) {
            if (totalTickets - count < 0) {
                return false;
            } else {
                totalTickets = totalTickets - count;
                return true;
            }
        }
    }
    
    // 作用於 static 方法上
    private static synchronized void soutStatic() {
        System.out.println(Thread.currentThread().getName() + " is sleeping ");
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " : 1");
        System.out.println(Thread.currentThread().getName() + " finish sleeping ");
    }
    
    // 作用於普通方法上
    private synchronized void soutNonStatic() {
        System.out.println(Thread.currentThread().getName() + " is sleeping ");
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " : 1");
        System.out.println(Thread.currentThread().getName() + " finish sleeping ");
    }
    
    //...
}

反編譯後:

關注 monitorenter、monitorexit、ACC_SYNCHRONIZED 這三個指令。

// 作用於對象上
private static boolean sellTicketsWithSyncObject(int);
descriptor: (I)Z
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
Code:
  stack=2, locals=3, args_size=1
    0: getstatic     #10                 // Field LOCK:Ljava/lang/Object;
    3: dup
    4: astore_1
    5: monitorenter
    ...
    16: monitorexit
    17: ireturn
    ...
    27: aload_1
    28: monitorexit
    29: ireturn
    ...
    31: aload_1
    32: monitorexit
    33: aload_2
    34: athrow
    ...
    frame_type = 75 /* same_locals_1_stack_item */
      stack = [ class java/lang/Throwable ]

...
// 作用於 static 上
private static synchronized void soutStatic();
    descriptor: ()V
    flags: (0x002a) ACC_PRIVATE, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    ...
    frame_type = 4 /* same */

...
// 作用於 普通方法 上
private synchronized void soutNonStatic();
    descriptor: ()V
    flags: (0x0022) ACC_PRIVATE, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=2, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    ...
    frame_type = 4 /* same */

4.1 原理分析

-> 作用於 代碼塊(對象) 上:

通過 monitor 來控制,使用monitorenter 和 monitorexit 指令控制線程進出。

monitor 講解

是一個監視器,執行是依賴於對象的鎖計數

  1. monitorenter:每個對象都是一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的所有權,過程如下:
    1. 如果monitor的進入數爲0,則該線程進入monitor,然後將進入數設置爲1,該線程即爲monitor的所有者
    2. 如果線程已經佔有該monitor,只是重新進入,則進入monitor的進入數加1
    3. 如果其他線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數爲0,再重新嘗試獲取monitor的所有權
  2. monitorexit:執行monitorexit的線程必須是objectref所對應的monitor的所有者。指令執行時,monitor的進入數減1,如果減1後進入數爲0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去獲取這個 monitor 的所有權。

-> 作用於 方法

  • 方法使用 ACC_SYNCHRONIZED 標識
  • static 方法,則鎖是作用在類上
  • 非 static 方法,則鎖作用在類對象上

喜歡就點個讚唄~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章