天天看點

JUC并發程式設計基礎學習之徹底了解八鎖問題

前言:

大家好,今天我們一起來學習JUC并發程式設計中的八鎖問題!希望你在看完本文後會對八鎖問題徹底了解!

1.1 隻存在一個資源類

模拟場景:

隻存在一個資源對象,判斷手機是先打電話還是先發短信?

1.讓發短信休眠

1-1 問題思考

思考1:

隻存在一個資源對象,如果将線程B(發短信)進行休眠,先執行打電話還是發短信?
1-2 代碼實作
package com.kuang.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @ClassName LockTest1
 * @Description 徹底了解八鎖問題
 * 模拟場景:隻存在一個資源對象,手機先打電話還是先發短信
 * @Author 狂奔の蝸牛rz
 * @Date 2021/7/27
 */
public class LockTest1 {

    public static void main(String[] args) throws InterruptedException {

        //擷取資源對象的執行個體
        Phone phone = new Phone();

        //使用Lambda表達式建立和啟動線程
        //線程A
        new Thread(()-> {
            //打電話
            phone.call();
        },"A").start();

        //讓發短信休眠1s
        try{
            //設定休眠時間為1s
            TimeUnit.SECONDS.sleep(1);
        //捕獲中斷異常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //線程B
        new Thread(()-> {
            //發短信
           phone.sendMsg();
        },"B").start();
    }

}

//資源類:Phone手機
class Phone {

    //打電話
    public synchronized void call() {
        System.out.println("打電話");
    }

    //發短信
    public synchronized void sendMsg() {
        System.out.println("發短信");
    }

}
           
1-3 測試結果
JUC并發程式設計基礎學習之徹底了解八鎖問題

結果:先執行打電話!

1-4 結果分析

為什麼先執行打電話?

錯誤回答:

因為線程A比線程B先執行,是以先執行打電話再執行發短

正确回答:

因為我們使用了synchronized同步鎖,然後給線程B(發短信)設定了休眠時間,是以它會抱着鎖睡覺,是以先執行打電話!

2.讓打電話休眠

2-1 問題思考

思考2:

隻存在一個資源對象,如果将線程B(發短信)休眠1s,線程A(打電話)休眠2s,結果仍然是先執行打電話再執行發短信嗎?
2-2 代碼實作
package com.kuang.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @ClassName LockTest1
 * @Description 徹底了解八鎖問題
 * 模拟場景:隻存在一個資源對象,手機先打電話還是先發短信
 * @Author 狂奔の蝸牛rz
 * @Date 2021/7/27
 */
public class LockTest1 {
public static void main(String[] args) throws InterruptedException {

        //擷取資源對象的執行個體
        Phone phone = new Phone();

        //使用Lambda表達式建立和啟動線程
        //線程A
        new Thread(()-> {
            //打電話
            phone.call();
        },"A").start();

        //讓發短信休眠1s
        try{
            //設定休眠時間為1s
            TimeUnit.SECONDS.sleep(1);
        //捕獲中斷異常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //線程B
        new Thread(()-> {
            //發短信
            phone.sendMsg();
        },"B").start();
    }

}

//資源類:Phone手機
class Phone {

    //打電話
    public synchronized void call() {
        //讓打電話休眠2s
        try{
            //設定休眠時間為2s
            TimeUnit.SECONDS.sleep(2);
            //捕獲中斷異常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打電話");
    }

    //發短信
    /**
     * synchronized(同步鎖)所鎖定的對象是方法的調用者,
     * 兩個方法用的是同一個鎖,誰先擷取鎖,誰先執行!
     */
    public synchronized void sendMsg() {
        System.out.println("發短信");
    }
    
}
           
2-3 測試結果
JUC并發程式設計基礎學習之徹底了解八鎖問題

結果:仍然先執行打電話!

2-4 結果分析

為什麼仍然是先執行打電話?

由于synchronized同步鎖所鎖定的Phone對象是這兩個方法的調用者,是以兩個方法用的是同一個鎖,誰先擷取鎖,誰就先執行!

雖然我們又給線程A(打電話)設定了2s休眠時間,但是線程A并不會立即解鎖,而是會抱着鎖睡覺,是以仍然是線程A(打電話)先執行!

3.調用非同步方法hello

3-1 問題思考

思考3:

隻存在一個資源對象,如果将線程B(hello)休眠1s,線程A(打電話)休眠2s,結果是先執行打電話還是hello?
3-2 代碼實作
package com.kuang.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @ClassName LockTest1
 * @Description 徹底了解八鎖問題
 * 模拟場景:隻存在一個資源對象,手機先打電話還是先發短信
 * @Author 狂奔の蝸牛rz
 * @Date 2021/7/27
 */
public class LockTest1 {
public static void main(String[] args) throws InterruptedException {

        //擷取資源對象的執行個體
        Phone phone = new Phone();

        //使用Lambda表達式建立和啟動線程
        //線程A
        new Thread(()-> {
            //打電話
            phone.call();
        },"A").start();

        //讓發短信休眠1s
        try{
            //設定休眠時間為1s
            TimeUnit.SECONDS.sleep(1);
        //捕獲中斷異常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //線程B
        new Thread(()-> {
            //線上程B中調用hello方法
            phone.hello();
        },"B").start();
	}

}

//資源類:Phone手機
class Phone {

    //打電話
    public synchronized void call() {
        //讓打電話休眠2s
        try{
            //設定休眠時間為2s
            TimeUnit.SECONDS.sleep(2);
            //捕獲中斷異常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打電話");
    }

    //發短信
    public synchronized void sendMsg() {
        System.out.println("發短信");
    }
    
    //hello方法(沒有使用synchronized同步鎖)
    public void hello() {
        System.out.println("hello");
    }
}
           
3-3 測試結果
JUC并發程式設計基礎學習之徹底了解八鎖問題

結果:先執行hello!

3-4 結果分析

為什麼先執行hello?

由于hello方法中沒有使用synchronized同步鎖,是以不受鎖的影響,是以先執行hello方法

1.2 存在兩個資源類

1. 問題思考

存在兩個資源對象,如果将線程B(發短信)休眠1s,線程A(打電話)休眠2s,先執行打電話還是發短信?

2.代碼實作

package com.kuang.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @ClassName LockTest2
 * @Description 徹底了解八鎖問題
 * 模拟場景:手機先打電話還是先發短信(存在兩個資源對象)
 * @Author 狂奔の蝸牛rz
 * @Date 2021/7/27
 */
public class LockTest2 {
    public static void main(String[] args) throws InterruptedException {
        //擷取兩個Phone對象執行個體
        Phones phone1 = new Phones();
        Phones phone2 = new Phones();

        //使用Lambda表達式建立和啟動線程
        //線程A
        new Thread(()-> {
            //打電話
            phone1.call();
        },"A").start();

        //讓發短信休眠1s
        try{
            //設定休眠時間為1s
            TimeUnit.SECONDS.sleep(1);
            //捕獲中斷異常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //線程B
        new Thread(()-> {
            //發短信
            phone2.sendMsg();
        },"B").start();
    }

}

//資源類:Phones電話
class Phones {

    //打電話
    public synchronized void call() {
        //讓打電話休眠2s
        try{
            //設定休眠時間為2s
            TimeUnit.SECONDS.sleep(2);
            //捕獲中斷異常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打電話");
    }

    //發短信
    public synchronized void sendMsg() {
        System.out.println("發短信");
    }
    
}
           

3.測試結果

JUC并發程式設計基礎學習之徹底了解八鎖問題

結果:先執行發短信!

4.結果分析

為什麼先執行發短信?

由于現在存在兩個資源對象執行個體,也就是兩個調用者,兩把鎖,是以這兩個方法不是同一個鎖,也就不存在誰先拿到鎖,誰先執行!

而線程A(打電話)又被設定為休眠2s,線程B(發短信)被設定為休眠1s,是以執行時間比發短信晚1s,是以先執行發短信!

1.3 隻存在一個資源類都為靜态方法

1. 問題思考

隻存在一個資源對象,如果将sendMsg(發短信)方法和call(打電話)同步方法都使用static修飾為靜态方法,并且線程B(發短信)休眠1s,線程A(打電話)休眠2s,先執行打電話還是發短信?

2.代碼實作

package com.kuang.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @ClassName LockTest3
 * @Description 徹底了解八鎖問題
 * (隻存在一個資源對象,方法修改為靜态方法)
 * 模拟場景:手機先打電話還是先發短信
 * @Author 狂奔の蝸牛rz
 * @Date 2021/7/27
 */
public class LockTest3 {

    public static void main(String[] args) throws InterruptedException {
        //擷取TelPhone對象執行個體
        TelPhone phone = new TelPhone();

        //使用Lambda表達式建立和啟動線程
        //線程A
        new Thread(()-> {
            //打電話
            phone.call();
        },"A").start();

        //讓發短信休眠1s
        try{
            //設定休眠時間為1s
            TimeUnit.SECONDS.sleep(1);
            //捕獲中斷異常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //線程B
        new Thread(()-> {
            //發短信
            phone.sendMsg();
        },"B").start();
    }

}

//資源類:TelPhone電話
//TelPhone是隻有唯一一個執行個體的Class對象
class TelPhone {

    //使用static修飾以下同步方法,使其成為靜态方法

    //打電話
    /**
     *  synchronized鎖的對象是方法的調用者
     *  static靜态方法,類一加載就有了(即Class模闆,鎖的是Class)
     */
    public static synchronized void call() {
        //讓打電話休眠2s
        try{
            //設定休眠時間為2s
            TimeUnit.SECONDS.sleep(2);
            //捕獲中斷異常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打電話");
    }

    //發短信
    public static synchronized void sendMsg() {
        System.out.println("發短信");
    }

}
           

3. 測試結果

JUC并發程式設計基礎學習之徹底了解八鎖問題

結果:先執行打電話!

4.結果分析

為什麼先執行打電話?

由于使用了static修飾了所有的同步方法,在TelPhone類一加載時就有了,屬于同一個Class模闆,并且TelPhones類是一個唯一的Class對象,是以鎖的仍然是同一個Class類(即實際上隻存在一個調用者);

雖然線程A(打電話)進行了2s的線程休眠,但是它沒有釋放鎖(相當于抱着鎖睡覺),是以先執行的是打電話

1.4 存在兩個資源類隻有一個靜态方法

1. 問題思考

存在兩個資源對象,如果将call(打電話)同步方法使用static修飾為靜态方法,并且線程B(發短信)休眠1s,線程A(打電話)休眠2s,先執行打電話還是發短信?

2.代碼實作

package com.kuang.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @ClassName LockTest4
 * @Description 徹底了解八鎖問題
 * (存在兩個資源對象,隻用打電話使用static修飾)
 * 模拟場景:手機先打電話還是先發短信
 * @Author 狂奔の蝸牛rz
 * @Date 2021/7/27
 */
public class LockTest4 {

    public static void main(String[] args) throws InterruptedException {
        //擷取兩個TelPhone2對象執行個體
        TelPhones phone1 = new TelPhones();
        TelPhones phone2 = new TelPhones();

        //使用Lambda表達式建立和啟動線程
        //線程A
        new Thread(()-> {
            //打電話
            phone1.call();
        },"A").start();

        //讓發短信休眠1s
        try{
            //設定休眠時間為1s
            TimeUnit.SECONDS.sleep(1);
            //捕獲中斷異常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //線程B
        new Thread(()-> {
            //發短信
            phone2.sendMsg();
        },"B").start();
    }

}

//資源類:TelPhones電話
//TelPhones是隻有唯一一個執行個體的Class對象
class TelPhones {

    //打電話
    //使用static修飾該方法,使其成為靜态方法
    //靜态的同步方法鎖的是Class類模闆(即TelPhone2)
    public static synchronized void call() {
        //讓打電話休眠2s
        try{
            //設定休眠時間為2s
            TimeUnit.SECONDS.sleep(2);
            //捕獲中斷異常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打電話");
    }

    //發短信
    //普通的同步方法鎖的是方法的調用者(即phone2)
    public synchronized void sendMsg() {
        System.out.println("發短信");
    }
}
           

3. 測試結果

JUC并發程式設計基礎學習之徹底了解八鎖問題

結果:先執行發短信!

4.結果分析

為什麼先執行發短信?

雖然存在兩個資源對象,理應存在兩個調用者,隻使用了static修飾了call(打電話)的同步方法, 是以 靜态同步方法call() 鎖的是對應的Class類模闆(TelPhones),而 普通的同步方法senMsg() 鎖的是 其方法的的調用者 (TelPhones的對象執行個體phone2),是以 phone1和phone2 是兩個資源對象,即不同的調用者和鎖;

線程A(打電話)進行了2s的線程休眠,線程B(發短信)隻休眠了1s,是以先執行的是發短信!

1.5 存在兩個資源類都為靜态方法

1. 問題思考

存在兩個資源對象,如果将sendMsg(發短信)方法和call(打電話)同步方法都使用static修飾為靜态方法,并且線程B(發短信)休眠1s,線程A(打電話)休眠2s,先執行打電話還是發短信?

2.代碼實作

package com.kuang.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @ClassName LockTest4
 * @Description 徹底了解八鎖問題
 * (存在兩個資源對象,并且都使用static修飾)
 * 模拟場景:手機先打電話還是先發短信
 * @Author 狂奔の蝸牛rz
 * @Date 2021/7/27
 */
public class LockTest5 {

        public static void main(String[] args) throws InterruptedException {
            //擷取兩個TelPhone對象執行個體
            //使用static修飾,兩個對象執行個體的Class類模闆隻有一個,鎖的是同一個Class類
            TelPhones2 phone1 = new TelPhones2();
            TelPhones2 phone2 = new TelPhones2();

            //使用Lambda表達式建立和啟動線程
            //線程A
            new Thread(()-> {
                //打電話
                phone1.call();
            },"A").start();

            //讓發短信休眠1s
            try{
                //設定休眠時間為1s
                TimeUnit.SECONDS.sleep(1);
                //捕獲中斷異常
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            //線程B
            new Thread(()-> {
                //發短信
                phone2.sendMsg();
            },"B").start();
        }

    }

//資源類:TelPhones2電話
//TelPhones2是隻有唯一一個執行個體的Class對象
class TelPhones2 {
    //使用static修飾以下同步方法,使其成為靜态方法

    //打電話
    public static synchronized void call() {
        //讓打電話休眠2s
        try{
            //設定休眠時間為2s
            TimeUnit.SECONDS.sleep(2);
            //捕獲中斷異常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打電話");
    }

    //發短信
    public static synchronized void sendMsg() {
        System.out.println("發短信");
    }
}
           

3. 測試結果

JUC并發程式設計基礎學習之徹底了解八鎖問題

結果:先執行打電話!

4.結果分析

為什麼先執行打電話?

雖然存在兩個資源對象,理應存在兩個調用者,但由于使用了static修飾了所有的同步方法,在TelPhone類一加載時就有了,屬于同一個Class模闆,并且TelPhones類是一個唯一的Class對象,是以鎖的仍然是同一個Class類(即實際上隻存在一個調用者);

線程A(打電話)雖然進行了2s的線程休眠,但是它 沒有釋放鎖(相當于抱着鎖睡覺),線程B(發短信)無法擷取鎖,是以先執行的仍然是打電話

到這裡,今天的有關八鎖問題的學習就結束了,歡迎大家學習和讨論!

參考視訊連結:https://www.bilibili.com/video/BV1B7411L7tE (B站UP主遇見狂神說的JUC并發程式設計基礎)