理解多線程“鎖”的機制

理解“鎖”

Java多線程最難理解的鎖的機制,下面會通過實例代碼來理解

實例一

同一對象調用同步塊裏的方法

package com.leo.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: Leo
 * @createDate: 2020/2/27
 * @version: 1.0
 */
public class Test01
{
    public static void main(String[] args)
    {
        Phone phone = new Phone();
        new Thread(() ->
        {
            phone.sendMsg();
        },"A").start();
        try
        {
            TimeUnit.SECONDS.sleep(1);//延時1秒
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        new Thread(()->{phone.call();},"B").start();
    }
}

class Phone
{
    public synchronized void sendMsg()
    {
        try
        {
            TimeUnit.SECONDS.sleep(4);//延時4秒
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println("發送消息!");
    }

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

通過創建一個Phone對象,裏面定義兩個同步方法,然後創建A/B兩個線程來調用下面的方法,判斷這兩個方法誰先調用!

synchronized同步方法,誰調用他就鎖誰,這裏我們看到只有一個phone對象去調用它,所以根據代碼的編寫來看,運行4秒後會先執行發短信,隨機執行打電話的方法;因爲這兩個方法是同一個鎖,誰寫被拿到誰執行。

實例二

package com.leo.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: Leo
 * @createDate: 2020/2/27
 * @version: 1.0
 */
public class Test02
{
    //發短信和hello誰會先執行?
    public static void main(String[] args)
    {
        Phone2 phone = new Phone2();
        new Thread(() ->
        {
            phone.sendMsg();
        }, "A").start();
        try
        {
            TimeUnit.SECONDS.sleep(2);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        new Thread(() ->
        {
            phone.hello();
        }, "B").start();
    }
}

class Phone2
{
    public synchronized void sendMsg()
    {
        try
        {
            TimeUnit.SECONDS.sleep(4);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println("發送消息!");
    }

    public void hello(){
        System.out.println("hello!");
    }
}

這裏Phone2類裏有一個發短信的同步方法,還有一個hello普通方法,現在依舊是兩個線程,調用發消息和打印hello,誰會先被打印?這裏是hello先被打印,因爲hello方法並沒有被鎖,所以在msg方法延遲4秒的時候他不需要等待,只需要按照上面代碼定義的延遲兩秒打印hello,然後到了第四秒打印消息。

實例三

兩個對象執行同步方法

package com.leo.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: Leo
 * @createDate: 2020/2/27
 * @version: 1.0
 */
public class Test02
{
    //發短信和hello誰會先執行?
    public static void main(String[] args)
    {
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();
        new Thread(() ->
        {
            phone1.sendMsg();
        }, "A").start();
        try
        {
            TimeUnit.SECONDS.sleep(2);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        new Thread(() ->
        {
            phone2.call();
        }, "B").start();
    }
}

class Phone2
{
    public synchronized void sendMsg()
    {
        try
        {
            TimeUnit.SECONDS.sleep(4);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println("發送消息!");
    }

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

這裏有兩個不同的對象,依據誰調用就鎖誰的原則,現在phone1調用就鎖phone1,這就是一把鎖。phone2調用就鎖phone2這是另一把鎖。所以現在有了兩把鎖。所以他鎖的肯定不是同一個對象,代碼就這麼走了,phone1去調用發短信的方法,發短信延遲4秒執行,然後phone2是另一把鎖,所以phone2並不需要等phone1執行完,他只是等待了2秒就把打電話打印了出來;然後到了第四秒打印了發消息。

實例四

單個對象調用靜態同步方法

package com.leo.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: Leo
 * @createDate: 2020/2/27
 * @version: 1.0
 */
public class Test03
{
    //
    public static void main(String[] args)
    {
        //現在有兩個對象,誰會先執行?
        Phone3 phone = new Phone3();
        new Thread(() ->
        {
            phone.sendMsg();
        }, "A").start();
        try
        {
            TimeUnit.SECONDS.sleep(2);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        new Thread(() ->
        {
            phone.call();
        }, "B").start();
    }
}

class Phone3
{
    public static synchronized void sendMsg()
    {
        try
        {
            TimeUnit.SECONDS.sleep(4);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println("發送消息!");
    }

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

加了static關鍵字之後,概念就完全不一樣了,static是靜態方法,在類一加載的時候就有了,所以他鎖的是class對象,這裏兩個方法都被static所修飾,所以他們鎖的是同一個對象,所以先調用的消息先被打印,然後打印電話。

實例五

兩個對象調用靜態同步方法

package com.leo.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: Leo
 * @createDate: 2020/2/27
 * @version: 1.0
 */
public class Test03
{
    //
    public static void main(String[] args)
    {
        //現在有兩個對象,誰會先執行?
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();
        new Thread(() ->
        {
            phone1.sendMsg();
        }, "A").start();
        try
        {
            TimeUnit.SECONDS.sleep(2);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        new Thread(() ->
        {
            phone2.call();
        }, "B").start();
    }
}

class Phone3
{
    public static synchronized void sendMsg()
    {
        try
        {
            TimeUnit.SECONDS.sleep(4);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println("發送消息!");
    }

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

現在有多了一個對象來調用同步塊裏的方法,那麼誰先打印呢?很顯然執行結果依舊不變,因爲上面提過static同步塊鎖的是class對象,phone3雖然被實例兩次,但是class對象只有一個。

實例六

單個對象調用靜態同步方法與普通同步方法

package com.leo.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: Leo
 * @createDate: 2020/2/27
 * @version: 1.0
 */
public class Test04
{
    //
    public static void main(String[] args)
    {
        //現在有兩個對象,誰會先執行?
        Phone4 phone = new Phone4();
        new Thread(() ->
        {
            phone.sendMsg();
        }, "A").start();
        try
        {
            TimeUnit.SECONDS.sleep(2);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        new Thread(() ->
        {
            phone.call();
        }, "B").start();
    }
}

class Phone4
{
    public static synchronized void sendMsg()
    {
        try
        {
            TimeUnit.SECONDS.sleep(4);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println("發送消息!");
    }

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


到了這就很瞭然了,擺明了這是兩把鎖,靜態方法塊鎖的class對象,普通同步方法鎖的是調用者。所以後面的打電話不需要去等待前面的方法。所以打電話先執行。

實例七

兩個對象調用靜態同步方法與普通同步方法

package com.leo.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: Leo
 * @createDate: 2020/2/27
 * @version: 1.0
 */
public class Test04
{
    //
    public static void main(String[] args)
    {
        //現在有兩個對象,誰會先執行?
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();
        new Thread(() ->
        {
            phone1.sendMsg();
        }, "A").start();
        try
        {
            TimeUnit.SECONDS.sleep(2);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        new Thread(() ->
        {
            phone2.call();
        }, "B").start();
    }
}

class Phone4
{
    public static synchronized void sendMsg()
    {
        try
        {
            TimeUnit.SECONDS.sleep(4);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println("發送消息!");
    }

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

現在我們就知道套路是什麼了,我們研究鎖的執行,就先看他們鎖的是不是同一個對象,看這裏跟上面一樣,鎖的依然不是一個對象,所以打電話不用等就執行了,執行結果跟上面一樣。

原來源:https://www.bilibili.com/video/av90007319?p=10

通過這些例子對多線程鎖的理解一定會更爲透徹。我本人是看了兩遍,加上碼這篇文章又複習了一遍。一共是三遍,當你看到狂總問你的題目,你是心裏完全有正確思路的時候,就說明你已經掌握了,如果三遍還不行那就五遍八遍,書讀百遍其義自見。

Author By 朝花不遲暮

Learn From 狂神說

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章