帶你深入理解和解剖 synchronized

1. 是什么?

synchronized 是 JAVA 關(guān)鍵字,用在方法或代碼塊??刂品椒ɑ虼a同一時(shí)間只有一個(gè)線程執(zhí)行,用來解決多線程同時(shí)訪問出現(xiàn)的并發(fā)問題。

2. 使用分類(作用域)

對(duì)象鎖(代碼塊)

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

鎖多個(gè)對(duì)象時(shí),相互不干擾

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;
        }
    }
}
  • 表現(xiàn)形式3:this當(dāng)前對(duì)象上
private boolean sellTicketsWithSyncObject(int count) {
    synchronized (this) {
        if (totalTickets - count < 0) {
            return false;
        } else {
            totalTickets = totalTickets - count;
            return true;
        }
    }
}

類鎖

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

對(duì)象鎖和類鎖區(qū)別?

對(duì)象鎖鎖的是一個(gè)對(duì)象。不同對(duì)象下互不干擾。而類只有一個(gè)。

3. synchronized 的特征

  1. 它是一個(gè)可重入鎖。
  2. JAVA 語言的關(guān)鍵字,由虛擬機(jī)字節(jié)碼指令實(shí)現(xiàn)。(下面深入講解)
    1. 用 monitor 來控制進(jìn)出
    2. moniterenter 進(jìn)入
    3. Monitorexit 為退出
  3. 不可中斷,無法控制鎖的超時(shí)時(shí)間,可能會(huì)造成阻塞。而 ReentranLock 可以,這個(gè)后面講解。
  4. synchronized 只有非公平鎖。 公平鎖和非公平鎖的區(qū)別
  5. 粒度是對(duì)象,鎖的是對(duì)象而非變量。

3.1 可重入講解

指的是同一線程的外層函數(shù)獲得鎖之后,內(nèi)層函數(shù)可以直接再次獲取該鎖。從而避免死鎖。

演示:

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();
        
        // 當(dāng)然也可以重入其他類方法,這里不演示了。
    }
}

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

如何反編譯?

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

以下是截取的部分源碼:

public class SynchronizedMethodDif {
    //...

    // 作用于對(duì)象上
    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 ");
    }
    
    //...
}

反編譯后:

// 作用于對(duì)象上
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 原理分析

-> 作用于 代碼塊(對(duì)象) 上:

通過 monitor 來控制,使用monitorenter 和 monitorexit 指令控制線程進(jìn)出。

monitor 講解:

是一個(gè)監(jiān)視器,執(zhí)行是依賴于對(duì)象的鎖計(jì)數(shù)

  1. monitorenter:每個(gè)對(duì)象都是一個(gè)監(jiān)視器鎖(monitor)。當(dāng)monitor被占用時(shí)就會(huì)處于鎖定狀態(tài),線程執(zhí)行monitorenter指令時(shí)嘗試獲取monitor的所有權(quán),過程如下:
    1. 如果monitor的進(jìn)入數(shù)為0,則該線程進(jìn)入monitor,然后將進(jìn)入數(shù)設(shè)置為1,該線程即為monitor的所有者
    2. 如果線程已經(jīng)占有該monitor,只是重新進(jìn)入,則進(jìn)入monitor的進(jìn)入數(shù)加1
    3. 如果其他線程已經(jīng)占用了monitor,則該線程進(jìn)入阻塞狀態(tài),直到monitor的進(jìn)入數(shù)為0,再重新嘗試獲取monitor的所有權(quán)
  2. monitorexit:執(zhí)行monitorexit的線程必須是objectref所對(duì)應(yīng)的monitor的所有者。指令執(zhí)行時(shí),monitor的進(jìn)入數(shù)減1,如果減1后進(jìn)入數(shù)為0,那線程退出monitor,不再是這個(gè)monitor的所有者。其他被這個(gè)monitor阻塞的線程可以嘗試去獲取這個(gè) monitor 的所有權(quán)。

-> 作用于 方法

  • 方法使用 ACC_SYNCHRONIZED 標(biāo)識(shí)
  • static 方法,則鎖是作用在類上
  • 非 static 方法,則鎖作用在類對(duì)象上
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容