前言:
大家好,今天我們一起來學習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 測試結果

結果:先執行打電話!
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 測試結果
結果:仍然先執行打電話!
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 測試結果
結果:先執行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.測試結果
結果:先執行發短信!
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. 測試結果
結果:先執行打電話!
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. 測試結果
結果:先執行發短信!
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. 測試結果
結果:先執行打電話!
4.結果分析
為什麼先執行打電話?
雖然存在兩個資源對象,理應存在兩個調用者,但由于使用了static修飾了所有的同步方法,在TelPhone類一加載時就有了,屬于同一個Class模闆,并且TelPhones類是一個唯一的Class對象,是以鎖的仍然是同一個Class類(即實際上隻存在一個調用者);
線程A(打電話)雖然進行了2s的線程休眠,但是它 沒有釋放鎖(相當于抱着鎖睡覺),線程B(發短信)無法擷取鎖,是以先執行的仍然是打電話
到這裡,今天的有關八鎖問題的學習就結束了,歡迎大家學習和讨論!
參考視訊連結:https://www.bilibili.com/video/BV1B7411L7tE (B站UP主遇見狂神說的JUC并發程式設計基礎)