1. 程式人生 > 其它 >執行緒八鎖現象的理解

執行緒八鎖現象的理解

技術標籤:JUCjava多執行緒面試

八鎖即鎖的八個問題

一、正常情況下執行緒的執行順序

package demo_02;
// 八鎖問題
// 正常執行緒執行情況

public class Test_01 {
    public static void main(String[] args) {
        PrintMessage pm = new PrintMessage();
        new Thread(()->{pm.printA();},"A").start();
        new Thread(()->{pm.printB(
);},"B").start(); } } class PrintMessage{ public synchronized void printA(){ System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "A"); } public synchronized void printB(){ System.out.println(Thread.currentThread().getName(
)+" 輸出了 => " + "B"); } }

執行結果:在這裡插入圖片描述

可以看到這裡是先執行的printA,在執行的printB,可是原因是什麼呢?因為先呼叫的printA方法,再呼叫的printB方法嗎?答案是否定的

二、睡眠A執行緒3秒,執行緒的執行順序

有人可能會覺得是因為先呼叫的printA方法再呼叫的printB方法的問題,因此我們讓printA方法睡三秒

package demo_02;
// 八鎖問題


import java.util.concurrent.TimeUnit;

public class Test_01 {
    public
static void main(String[] args) { PrintMessage pm = new PrintMessage(); new Thread(()->{ try { pm.printA(); } catch (InterruptedException e) { e.printStackTrace(); } },"A").start(); new Thread(()->{pm.printB();},"B").start(); } } class PrintMessage{ public synchronized void printA() throws InterruptedException { TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "A"); } public synchronized void printB(){ System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "B"); } }

執行結果:在這裡插入圖片描述

不管執行多少次都是先列印A再列印B,注意這裡並不是因為先呼叫的printA方法,再呼叫printB方法的問題,這樣理解根本沒有理解到關鍵。
這裡出現先列印A再列印B的原因是因為大家沒有理解synchronized鎖,鎖的是什麼?這個地方鎖的是PrintMessage這個類的呼叫者pm物件,所以在A執行緒屌用printA方法時,會對pm物件加鎖,B執行緒要等待A執行緒釋放pm鎖之後才可以獲得pm物件。

三、普通方法和synchronized方法執行問題

package demo_02;
// 八鎖問題


import java.util.concurrent.TimeUnit;

public class Test_01 {
    public static void main(String[] args) {
        PrintMessage pm = new PrintMessage();
        new Thread(()->{
            try {
                pm.printA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{pm.printC();},"C").start();
    }
}
class PrintMessage{
    public synchronized void printA() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "A");
    }
    public void printC(){
        System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "C");
    }
}

執行結果:在這裡插入圖片描述

這裡為了讓結果看起來更清晰,我將A執行緒睡了三秒。這裡先列印C的原因是在A執行緒拿到了pm物件的鎖的時候,C執行緒可以直接對pm物件進行操作,因為printC方法並不是同步方法,不需要進行同步操作,A執行緒有沒有鎖對C執行緒呼叫printC方法都沒有影響。

四、兩個執行緒不同的物件執行順序

為了獲取觀察效果,在這裡同樣讓執行緒A睡3秒

package demo_02;
// 八鎖問題


import java.util.concurrent.TimeUnit;

public class Test_01 {
    public static void main(String[] args) {
        PrintMessage pm1 = new PrintMessage();
        PrintMessage pm2 = new PrintMessage();
        new Thread(()->{
            try {
                pm1.printA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{pm2.printB();},"B").start();
    }
}
class PrintMessage{
    public synchronized void printA() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "A");
    }
    public synchronized void printB(){
        System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "B");
    }
}

執行結果:在這裡插入圖片描述

這裡雖然是兩個同步方法,但是synchronized鎖的物件不同,因此執行緒A和執行緒B呼叫的printA和printB方法分別是不對物件的方法,兩個執行緒並不衝突,因為A執行緒睡眠的3秒,所以先列印B再列印A。

五、靜態同步方法的執行順序

將同步方法更改為static靜態方法,看一看執行順序有什麼不同。
static修飾的方法會在類第一次載入的時候被執行。

package demo_02;
// 八鎖問題


import java.util.concurrent.TimeUnit;

public class Test_01 {
    public static void main(String[] args) {
        PrintMessage pm = new PrintMessage();
        new Thread(()->{
            try {
                pm.printA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{pm.printB();},"B").start();
    }
}
class PrintMessage{
    public static synchronized void printA() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "A");
    }
    public static synchronized void printB(){
        System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "B");
    }
}

執行結果:
在這裡插入圖片描述

這裡雖然和前面的正常情況的輸出結果一樣,但是其內在原理卻不相同。首先我們需要理解一下static關鍵字,static宣告的靜態方法,會在類被載入的時候就執行。可以理解成static方法在物件被new的時候就已經執行,執行後的方法會放在class檔案中(java檔案編譯後產生的class檔案),class又被稱為該類的模板,一個類在執行過程只只會有一個模板這裡synchronized鎖,鎖的物件是一個模板,而不是一個類物件,雖然輸出結果一樣但是鎖的物件並不相同。
可能很多同學還不是太懂於是就有了第六個鎖的問題

六、兩個物件,兩個靜態同步方法

如果之前大家可能還沒有想通,我們建立兩個PrintMessage的物件,分別呼叫列印方法,按道理如果A執行緒睡眠3秒,那B執行緒就會先列印,然後在列印A執行緒的輸出方法,但是如果是靜態同步方法呢?

package demo_02;
// 八鎖問題


import java.util.concurrent.TimeUnit;

public class Test_01 {
    public static void main(String[] args) {
        PrintMessage pm1 = new PrintMessage();
        PrintMessage pm2 = new PrintMessage();
        new Thread(()->{
            try {
                pm1.printA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{pm2.printB();},"B").start();
    }
}
class PrintMessage{
    public static synchronized void printA() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "A");
    }
    public static synchronized void printB(){
        System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "B");
    }
}

執行結果:
在這裡插入圖片描述

七、一個靜態同步方和一個普通方法,一個物件的執行順序

package demo_02;
// 八鎖問題


import java.util.concurrent.TimeUnit;

public class Test_01 {
    public static void main(String[] args) {
        PrintMessage pm = new PrintMessage();
        new Thread(()->{
            try {
                pm.printA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{pm.printB();},"B").start();
    }
}
class PrintMessage{
    public static synchronized void printA() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "A");
    }
    public  void printB(){
        System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "B");
    }
}

執行結果:

相信大家如果認真看了前面的問題,就知道這個問題的原因了,因為普通方法並不會獲取鎖資源,所以執行緒B的普通方法可以直接執行,執行緒A睡眠3秒後再執行。

八、兩個物件,一個靜態同步方法,一個普通同步方法,執行順序

這個地方的關鍵是要明白鎖的物件鎖的是什麼?

package demo_02;
// 八鎖問題


import java.util.concurrent.TimeUnit;

public class Test_01 {
    public static void main(String[] args) {
        PrintMessage pm1 = new PrintMessage();
        PrintMessage pm2 = new PrintMessage();
        new Thread(()->{
            try {
                pm1.printA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{pm2.printB();},"B").start();
    }
}
class PrintMessage{
    public static synchronized void printA() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "A");
    }
    public synchronized void printB(){
        System.out.println(Thread.currentThread().getName()+" 輸出了 => " + "B");
    }
}

執行結果:
在這裡插入圖片描述

首先靜態同步方法鎖的是類模板(class檔案),而普通同步方法鎖的是PrintMessage的物件,所以兩個鎖的物件是不同的,兩個鎖並不衝突,自己鎖自己的,執行緒A睡眠3秒後輸出,執行緒B正常輸出,所以先輸出B執行緒再輸出A執行緒。

該部分內容是筆者參考狂神的學習視訊,做的一些總結以及自己的思考,如果有紕漏的地方,希望大家多多指出,一起學習!