JUC并发编程基础学习之彻底理解八锁问题

前言

大家好,今天我们一起来学习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并发编程基础)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狂奔の蜗牛rz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值