JUC並行程式設計學習筆記(四)8鎖現象

2023-11-03 06:01:06

8鎖現象

八鎖->就是關於鎖的八個問題

鎖是什麼,如何判斷鎖的是誰

物件、class模板

深刻理解鎖

鎖的東西無外乎就兩樣:1、同步方法的呼叫者,2、Class模板。

同一個鎖中,只有當前執行緒資源釋放後才會被下一個執行緒所接手。

同步方法的呼叫者是兩個不同的範例時,互不相關。

靜態同步方法(static)鎖的是整個Class模板,和同步方法的呼叫者也不是同一個鎖;切Class模板在Java程式中唯一。

程式碼範例

1、淺淺理解鎖的作用

同一把鎖中根據執行先後釋放資源,保證一個資源的使用順序

package org.example.phone;

import java.util.concurrent.TimeUnit;

public class Test1 {
    public static void main(String[] args) {
//        標準情況下,列印順序為 1、傳簡訊,2、打電話
//        給sendMsg內部延遲四秒執行,執行順序依舊是 1、傳簡訊,2、打電話
//        可知,並非是我們所想的,A執行緒在前面就先執行,而是鎖的機制導致了這種情況
//        phone1只建立了一個物件,所以這個物件的鎖只有一把,誰先拿到就是誰先執行
//        鎖的物件是該方法的呼叫者,即phone1
        Phone1 phone1 = new Phone1();
        new Thread(()->{
            phone1.sendMsg();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        new Thread(()->{
            phone1.call();
        },"B").start();
    }
}
class Phone1{
//    synchronized鎖的物件是方法的呼叫者,Phone1只new了一個物件,所以鎖的是new出來的整個物件
    public synchronized void sendMsg(){

        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("傳簡訊");
    }
    public synchronized void call(){
        System.out.println("打電話");
    }
}

2、區分鎖的物件

不同的範例使用的鎖並非同一把,所以也無法同時鎖定某個固定的資源、無法對同一資源進行有順序的操作

package org.example.phone;

import java.util.concurrent.TimeUnit;

public class Test3 {
    public static void main(String[] args) {
//        標準情況下,列印順序為 1、傳簡訊,2、打電話
//        給sendMsg內部延遲四秒執行,執行順序依舊是 1、傳簡訊,2、打電話
//        可知,並非是我們所想的,A執行緒在前面就先執行,而是鎖的機制導致了這種情況
//        phone1只建立了一個物件,所以這個物件的鎖只有一把,誰先拿到就是誰先執行
//        鎖的物件是該方法的呼叫者,即phone1

//        呼叫兩個不同物件的方法,鎖的是兩個不同的物件,此時先出現打電話,說明不同物件之間的鎖互不影響
        Phone3 phone3_1 = new Phone3();
        Phone3 phone3_2 = new Phone3();
        new Thread(()->{
            phone3_1.sendMsg();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        new Thread(()->{
            phone3_2.call();
        },"B").start();
    }
}
class Phone3{
    //    synchronized鎖的物件是方法的呼叫者,Phone1只new了一個物件,所以鎖的是new出來的整個物件
    public synchronized void sendMsg(){

        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("傳簡訊");
    }
    public synchronized void call(){
        System.out.println("打電話");
    }
    //    當在資源類中新增了一個普通方法後,先輸出hello
//    沒有鎖,不是同步方法,不受鎖的影響
    public void hello(){
        System.out.println("Hello");
    }
}

3、瞭解鎖的參與者

只有同步方法參與鎖,普通方法依舊按照java執行順序執行

package org.example.phone;

import java.util.concurrent.TimeUnit;

public class Test2 {
    public static void main(String[] args) {
//        標準情況下,列印順序為 1、傳簡訊,2、打電話
//        給sendMsg內部延遲四秒執行,執行順序依舊是 1、傳簡訊,2、打電話
//        可知,並非是我們所想的,A執行緒在前面就先執行,而是鎖的機制導致了這種情況
//        phone1只建立了一個物件,所以這個物件的鎖只有一把,誰先拿到就是誰先執行
//        鎖的物件是該方法的呼叫者,即phone1
        Phone2 phone2 = new Phone2();
        new Thread(()->{
            phone2.sendMsg();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        new Thread(()->{
            phone2.hello();
        },"B").start();
    }
}
class Phone2{
    //    synchronized鎖的物件是方法的呼叫者,Phone1只new了一個物件,所以鎖的是new出來的整個物件
    public synchronized void sendMsg(){

        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("傳簡訊");
    }
    public synchronized void call(){
        System.out.println("打電話");
    }
//    當在資源類中新增了一個普通方法後,先輸出hello
//    沒有鎖,不是同步方法,不受鎖的影響
    public void hello(){
        System.out.println("Hello");
    }
}

4、明白鎖能鎖誰

鎖只能鎖兩個東西,一個是同步方法的呼叫者,一個是整個Class模板(全域性唯一),一旦使用static建立靜態同步方法,那麼該方法的鎖鎖的就是全域性唯一的Class模板,並且在反射時就已經被建立了

package org.example.phone;

import java.util.concurrent.TimeUnit;

public class Test4 {
    public static void main(String[] args) {
//        兩個物件的Class類別範本只有一個;static,鎖的是Class
        Phone4 phone4_1 = new Phone4();
        Phone4 phone4_2 = new Phone4();
        new Thread(()->{
            phone4_1.sendMsg();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        new Thread(()->{
            phone4_2.call();
        },"B").start();
    }
}
class Phone4{
//    synchronized鎖的物件是方法的呼叫者
//    注:增加了static靜態方法 此時呼叫該方法的就變成了Phone4的反射物件,全域性唯一
//    此時鎖的就是Class模板了,即不管你有幾個呼叫者,都在同一個鎖
//    static方法類一載入就有了!鎖的是Class
    public static synchronized void sendMsg(){

        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("傳簡訊");
    }
    public static synchronized void call(){
        System.out.println("打電話");
    }
}

5、深入理解鎖的是誰

靜態同步方法和普通同步方法在一起使用時,鎖的並非同一物件,所以列印順序也時按java的執行順序來,並不存在鎖定資源的情況

package org.example.phone;

import java.util.concurrent.TimeUnit;
/*
* 1、一個靜態同步方法,一個普通同步方法,先列印傳簡訊還是打電話
*   兩個方法一個鎖的是Class模板,一個鎖的是呼叫者,鎖的不是同一物件,所以延遲四秒的靜態同步方法後列印,延遲一秒的普通同步方法先列印
*
* */
public class Test5 {
    public static void main(String[] args) {

        Phone5 phone5_1 = new Phone5();
//        Phone5 phone5_2 = new Phone5();
        new Thread(()->{
            phone5_1.sendMsg();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        new Thread(()->{
            phone5_1.call();
        },"B").start();
    }
}
class Phone5{
//    鎖的是Class模板
    public static synchronized void sendMsg(){

        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("傳簡訊");
    }
//    鎖的是呼叫者
    public synchronized void call(){
        System.out.println("打電話");
    }
}