多執行緒之8鎖問題

農夫三拳有點疼~發表於2020-05-02

Phone 有兩個方法:傳送郵件和傳送簡訊,每個方法都列印一句話,現在通過不同的方式對方法進行操作,回答出列印的先後順序(建議先自己看程式碼認真思考,然後再看答案,文章結尾會對每個問題進行分析)

問題

1、標準訪問,兩執行緒中間睡眠 2 毫秒,先列印郵件還是簡訊?

class Phone {
    public synchronized void sendEmail() {
        System.out.println("send email");
    }
    public synchronized void sendSms() {
        System.out.println("send sms");
    }
}

public class Lock01 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(() -> phone.sendEmail(), "A").start();
        Thread.sleep(200);
        new Thread(() -> phone.sendSms(), "B").start();
    }
}
檢視答案 send email
send sms

2、在 sendEmail() 方法中睡眠 4 秒,先列印郵件還是簡訊?

class Phone {
    public synchronized void sendEmail() {
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send email");
    }
    public synchronized void sendSms() {
        System.out.println("send sms");
    }
}

public class Lock02 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(() -> phone.sendEmail(), "A").start();
        Thread.sleep(200);
        new Thread(() -> phone.sendSms(), "B").start();
    }
}
檢視答案 send email
send sms

3、新增普通的 hello() 方法,先列印郵件還是 hello?

class Phone {
    public synchronized void sendEmail() {
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send email");
    }
    public void hello() {
        System.out.println("hello");
    }
}
public class Lock03 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(() -> phone.sendEmail(), "A").start();
        Thread.sleep(200);
        new Thread(() -> phone.hello(), "B").start();
    }
}
檢視答案 hello
send email

4、2 個手機,先列印郵件還是簡訊?

class Phone {
    public synchronized void sendEmail() {
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send email");
    }

    public synchronized void sendSms() {
        System.out.println("send sms");
    }
}

public class Lock04 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> phone1.sendEmail(), "A").start();
        Thread.sleep(200);
        new Thread(() -> phone2.sendSms(), "B").start();
    }
}
檢視答案 send sms
send email

5、2個靜態同步方法,1部手機,先列印郵件還是簡訊?

class Phone {
    public static synchronized void sendEmail() {
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send email");
    }

    public static synchronized void sendSms() {
        System.out.println("send sms");
    }
}

public class Lock05 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(() -> phone.sendEmail(), "A").start();
        Thread.sleep(200);
        new Thread(() -> phone.sendSms(), "B").start();
    }
}
檢視答案 send email
send sms
**6、2個靜態同步方法,2部手機,先列印郵件還是簡訊?**
class Phone {
    public static synchronized void sendEmail() {
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send email");
    }

    public static synchronized void sendSms() {
        System.out.println("send sms");
    }
}

public class Lock06 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> phone1.sendEmail(), "A").start();
        Thread.sleep(200);
        new Thread(() -> phone2.sendSms(), "B").start();
    }
}
檢視答案 send email
send sms
**7、1個靜態同步方法,1個普通同步方法,1部手機,先列印郵件還是簡訊?**
class Phone {
    public static synchronized void sendEmail() {
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send email");
    }

    public synchronized void sendSms() {
        System.out.println("send sms");
    }
}

public class Lock07 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(() -> phone.sendEmail(), "A").start();
        Thread.sleep(200);
        new Thread(() -> phone.sendSms(), "B").start();
    }
}
檢視答案 send sms
send email

8、1個靜態同步方法,1個普通同步方法,2部手機,先列印郵件還是簡訊?

class Phone {
    public static synchronized void sendEmail() {
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send email");
    }

    public synchronized void sendSms() {
        System.out.println("send sms");
    }
}

public class Lock08 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> phone1.sendEmail(), "A").start();
        Thread.sleep(200);
        new Thread(() -> phone2.sendSms(), "B").start();
    }
}
檢視答案 send sms
send email

分析

? 問題一

當一個物件裡有多個同步(synchronized)方法,有一個執行緒訪問了其中一個同步方法,其它執行緒只能等待其訪問完成後才能訪問,因為此時鎖的是當前物件 this,其它的執行緒都不能進入到當前物件的其它的同步方法。

如果沒有新增 Thread.sleep(200); 則列印的順序是不一定的,因為執行緒的排程和作業系統有關。 新增 Thread.sleep(200); 則保證了執行緒 A 比 B 先執行。

? 問題二

由於執行緒 A 先執行,會先呼叫 sendEmail() 方法,Phone 例項就會被鎖住,執行緒 B 只能等待 A 執行完在執行。

? 問題三

hello() 方法並不是同步方法,因此不受鎖的影響。

? 問題四

現在有兩個例項,前面我們說過,synchronized 鎖的是 this,所以會產生兩把鎖,它們之間互不干擾,誰先執行完誰就先列印。

? 問題五、問題六

synchronized 實現同步的基礎:Java 中的每一個物件都可以作為鎖,具體表現為以下三種形式:

  • 對於普通同步方法,鎖的是當前例項物件 this
  • 對於靜態同步方法,鎖的是當前類的 Class 物件
  • 對於同步方法塊,鎖是Synchonized括號裡配置的物件

所以,無論是 1 個物件還是 2 個物件,靜態同步方法鎖的都是 Class,只能按照執行緒執行的順序列印。

? 問題七、問題八

這兩種情況都是 1 個靜態同步方法,1 個非靜態同步方法,它們的鎖都不是同一個物件,因此相互不受影響

總結

1、當一個執行緒試圖訪問同步程式碼塊時,它首先必須得到鎖,退出或丟擲異常時必須釋放鎖。

2、Java 中的每一個物件都可以作為鎖;普通同步方法鎖 this,靜態同步方法鎖 Class,同步方法塊鎖括號;

3、只要鎖的物件不是同一個,就直接按照執行緒執行的快慢來決定;鎖的物件是同一個,就按照執行緒進入的先後順序決定。

只要掌握了鎖的物件是什麼,無論是 8 鎖還是 100 鎖都不在話下!

相關文章