理解多线程“锁”的机制

理解“锁”

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 狂神说

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Curtisjia/article/details/104548102