JUC并发编程-8锁现象


首先感谢下 B站的狂神,在这里第一次听到“8锁现象”,并弄懂了“锁到底锁的是谁”。
8锁现象,实际对应的就是8个问题。
掌握了这8个问题后:可以清楚判断锁的是谁!知道什么是锁,锁到底锁的是谁!

锁方法的调用者

问题一

如下代码:标准情况下,下边两个线程先打印 发短信 还是 打电话 ?

public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{phone.seedMsg();}, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{phone.call();}, "B").start();
    }
}
class Phone{
    public synchronized void seedMsg(){
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

执行结果:先输出发短信,再输出打电话。
思考下为什么?
如果你是认为线程A先被调用的,那是不对的。现在抱着这个想法,再去思考下面的代码:

问题二

如下代码:将发短信的方法sleep四秒,两个线程先打印 发短信 还是 打电话 ?

public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{phone.seedMsg();}, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{phone.call();}, "B").start();
    }
}
class Phone{
    public synchronized void seedMsg(){
    	try {TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

执行结果:还是先输出发短信,再输出打电话。
思考下为什么?
首先知道 锁住的对象是谁?因为 synchronized 加在方法上,所以锁的对象是方法的调用者。如上示例:两条线程执行的方法调用者是不是都是new出来的phone 对象啊,所以两个方法用的是同一个锁,谁先拿到谁先执行!

通俗解释:
phone对象就是方法的调用者,也就是手机,它可以打电话和发短信。
现在有两个人线程A 和线程B,他们一个想打电话,一个想发短信。
线程A,先拿到锁(也就是手机),抱着锁(手机)睡了4秒。 线程B肯定拿不到锁(手机),需要等待。所以最终A睡了4秒醒了就执行输出了发短信,让后释放掉锁,线程B才能拿到锁执行输出打电话

问题三

如下代码:Phone类中新增一个普通方法hello(),线程B调用,那么两个线程先打印 发短信 还是 先输出hello?

public class Test2 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()->{ phone.seedMsg();}, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{phone.hello();}, "B").start();
    }
}
// 手机
class Phone2{
    //同步方法
    public synchronized void seedMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    //同步方法
    public synchronized void call(){
        System.out.println("打电话");
    }
}

执行结果:先输出了hello,再输出发短信。
思考下为什么?
因为hello()方法,是普通方法,根本不受锁的影响。线程B执行hello方法,不需要获取锁就能执行,所以也不必等待A执行完释放锁。

问题四

如下代码:创建两个phone对象,线程调用不同对象的方法,那么两个线程先打印 发短信 还是 打电话?

public class Test2 {
    public static void main(String[] args) {
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();
        // 线程A,
        new Thread(()->{phone1.seedMsg();}, "A").start();
        // 1秒延迟
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 线程B,人
        new Thread(()->{ phone2.call();}, "B").start();
    }
}

// 手机
class Phone2{
    //同步方法
    public synchronized void seedMsg(){
        // 4秒延迟
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    //同步方法
    public synchronized void call(){
        System.out.println("打电话");
    }
}

执行结果:先输出了打电话,再输出发短信。
思考下为什么?
还是那句话,synchronized用在方法上,那么锁的是方法的调用者。现在有两个调用者,就相当于你用你的手机发短信,我用我的手机打电话,所以互不影响啊。

锁class对象

问题五

如下代码:将方法变为静态同步方法,那么两个线程先打印 发短信 还是 打电话?

public class Test3 {
    public static void main(String[] args) {
        Phone3 phone = new Phone3();
        // 线程A,
        new Thread(()->{phone.seedMsg();}, "A").start();
        // 1秒延迟
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 线程B,
        new Thread(()->{phone.call();}, "B").start();
    }
}
// 手机
class Phone3{
    // 静态同步方法
    public static synchronized void seedMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    // 静态同步方法
    public static synchronized void call(){
        System.out.println("打电话");
    }
}

执行结果:先输出发短信,再输出打电话。
思考下为什么?
如果你的理解还是“synchronized用在方法上,那么锁的是方法的调用者”,那么就错了。别忘了方法用static修饰了。
static静态方法,在类初始化的时候,就加载到内存中生成了class对象。要知道java中所有的类,只有一个class对象,
这个class对象是唯一的,所以锁静态方法,其实锁的就是class对象。

问题六

如下代码:方法还是静态同步方法,但是我们创建两个对象来调方法,那么两个线程先打印 发短信 还是 打电话?

public class Test3 {
    public static void main(String[] args) {
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();
        new Thread(()->{phone1.seedMsg();}, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{ phone2.call();}, "B").start();
    }
}
// 手机
class Phone3{
    // 静态同步方法
    public static synchronized void seedMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    // 静态同步方法
    public static synchronized void call(){
        System.out.println("打电话");
    }
}

执行结果:先输出发短信,再输出打电话。
上边已经说了,锁静态方法,其实锁的就是class对象,java中所有的类,只有一个class对象,所以无论几个对象,使用的锁都是一样的。

同时锁方法的调用者和class对象

问题七

如下代码:一个是静态同步方法,一个普通同步方法,那么两个线程先打印 发短信 还是 打电话?

public class Test4 {
    public static void main(String[] args) {
        Phone4 phone1 = new Phone4();
        new Thread(()->{phone1.seedMsg();}, "A").start();
        // 1秒延迟
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 线程B,人
        new Thread(()->{phone1.call();}, "B").start();
    }
}
// 手机
class Phone4{
    // 静态同步方法
    public static synchronized void seedMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    // 普通同步方法
    public synchronized void call(){
        System.out.println("打电话");
    }
}

执行结果:先输出打电话,再输出发短信。
思考下为什么?
这还用想?两个锁,锁的东西都不一样啊,一个锁class对象,一个锁Phone4 对象,两个都是互不影响的。

问题八

如下代码:在上面的基础上,增加一个对象,两个对象分别调用静态和非静态的同步方法,那么两个线程先打印 发短信 还是 打电话?

public class Test4 {
    public static void main(String[] args) {
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();
        new Thread(()->{phone1.seedMsg();}, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{phone2.call();}, "B").start();
    }
}
// 手机
class Phone4{
    // 静态同步方法
    public static synchronized void seedMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    // 普通同步方法
    public synchronized void call(){
        System.out.println("打电话");
    }
}

相信到这里已经能肯定回答出“先输出打电话,再输出发短信了”。
不要怀疑,以为增加了一个对象,就会有什么影响?只要记住静态同步方法锁class对象,普通同步方法锁调用者就行。两个锁 锁住的东西不一样,所以互不影响的。

上一篇:JUC-Lock锁与Synchronized
下一篇:并发编程下如何使用安全的集合

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值