深入理解JUC的8锁现象

1 篇文章 0 订阅

8锁现象其实就是关于锁的8个问题。
8锁现象一直都是JavaJUC面试的高频考点。
简单来说,我们需要永远判断以下三个问题,即
如何判断锁的是谁!永远知道什么锁,锁到底锁的是谁!
下面通过一个简单的场景来彻底玩转8锁现象!
定义一个资源类People,里面有两个经过synchronized上锁后的方法,一个exercise(),方法里面执行输出跑步,一个study(),方法里面输出考研。

1.第一个问题

1.定义一个people对象,两个线程执行 先输出跑步还是考研?
具体代码如下所示:

package com.lock8;
import java.util.concurrent.TimeUnit;
/*
* 1.定义一个people对象,两个线程执行 先执行跑步还是考研?
* */
public class Test1 {
    public static void main(String[] args) {
        People people = new People();
        new Thread(()->{
            people.exercise();
        },"A").start();
        //这里睡眠1s 当然也可以不睡眠
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            people.study();
        },"B").start();
    }
}
class People{
    public synchronized void exercise(){
        System.out.println("跑步");
    }
    public synchronized void study(){
        System.out.println("考研");
    }
}

结果展示:
在这里插入图片描述
原因分析:
原因不仅仅是应为线程A的people.exercise()先执行,究其根本是因为在People资源类的方法里面有synchronized的存在,而synchronized的对象是方法的调用者,我们这里只有一个People实例,即只有一个调用者,所以此时两个方法用的是同一个锁,即谁先拿到该锁便谁先执行!

2.第二个问题

2.还是两个线程执行 但是exercise方法内部首先延迟4秒 此种情况下先输出跑步还是考研?

package com.lock8;
import java.util.concurrent.TimeUnit;

public class Test1 {
    public static void main(String[] args) {
        People people = new People();
        new Thread(()->{
            people.exercise();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            people.study();
        },"B").start();
    }
}

class People{
    public synchronized void exercise(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("跑步");
    }
    public synchronized void study(){
        System.out.println("考研");
    }
}

结果展示:
在这里插入图片描述
原因分析:
这里虽然在exercise方法内部延迟4s,但是我们在study和exercise方法上面加上了synchronized锁,且只定义了一个people对象,即两个线程的上锁的是同一个对象,即exercise在延迟的同时没有释放锁,所以study方法会在exercise方法执行之后再执行!

3.第三个问题

3.增加了一个普通方法exam(),在exam里面输出考试,同样是两个线程执行,一个线程执行exercise方法,一个线程执行非同步方法exam,是先输出跑步还是考试?

package com.lock8;
import java.util.concurrent.TimeUnit;

public class Test2 {
    public static void main(String[] args) {
        People1 people = new People1();
        new Thread(()->{
            people.exercise();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            people.exam();
        },"B").start();
    }
}

class People1{
    public synchronized void exercise(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("跑步");
    }
    public synchronized void study(){
        System.out.println("考研");
    }
    public void exam(){
        System.out.println("考试");
    }
}

结果展示:
在这里插入图片描述
原因分析:
由于非同步方法exam上面没有上synchronized锁,不受锁的影响,所以在exercise内部延迟的同时,程序就会先输出考试,接着等延迟过后在输出跑步!

4.第四个问题

4.定义两个资源对象,两个线程执行两个同步方法,先输出考研还是跑步?

package com.lock8;
import java.util.concurrent.TimeUnit;
public class Test2 {
    public static void main(String[] args) {
        People1 people1= new People1();
        People1 people2= new People1();
        new Thread(()->{
            people1.exercise();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            people2.study();
        },"B").start();
    }
}

class People1{
    public synchronized void exercise(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("跑步");
    }
    public synchronized void study(){
        System.out.println("考研");
    }
    public void exam(){
        System.out.println("考试");
    }
}

结果展示:
在这里插入图片描述
原因分析:
由于synchronized锁的是方法的调用者,但是我们这里定义了两个对象,即每个对象都有一把锁,就有两把锁,所以当第一个对象的exercise方法被延迟后,程序会自动调用第二个对象的study方法,因此锁的不是同一个对象,因此会先输出考研后输出跑步。

5.第五个问题

5.增加两个静态的同步方法,即在exercise方法和study方法前面加一个static关键字,将方法都变为静态方法

package com.lock8;
import java.util.concurrent.TimeUnit;

public class Test3 {
    public static void main(String[] args) {
        People2 people= new People2();
        new Thread(()->{
            people.exercise();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            people.study();
        },"B").start();
    }
}

class People2{
    public static synchronized void exercise(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("跑步");
    }
    public static synchronized void study(){
        System.out.println("考研");
    }
    public void exam(){
        System.out.println("考试");
    }
}

原因分析:
这里的方法用static关键字修饰,用static修饰的锁,锁的不是方法调用者本身,而锁的是类的模板,即Class模板,而众多的类实例只有一个类模板,因此锁的类模板也是全局唯一。所以只要exercise方法不释放锁,就不会执行study方法。

6.第六个问题

6.这里定义两个类对象,同样增加两个静态同步方法,先输出跑步还是考研?

package com.lock8;
import java.util.concurrent.TimeUnit;
public class Test3 {
    public static void main(String[] args) {
        People2 people1= new People2();
        People2 people2= new People2();
        new Thread(()->{
            people1.exercise();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            people2.study();
        },"B").start();
    }
}

class People2{
    public static synchronized void exercise(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("跑步");
    }
    public static synchronized void study(){
        System.out.println("考研");
    }
    public void exam(){
        System.out.println("考试");
    }
}

结果展示:
在这里插入图片描述
原因分析:
由于这次采用的static同步锁,因此锁的是类模板对象,而众多类实例的类模板全局唯一。(当类的.class文件经过类加载器后,就会将类模板初始化加载到JVM的方法区中,而众多的类实例都是通过类模板映射加载,可以输出众多实例的类模板的hashCode来证明!)同样的,由于上锁的是同一个对象,即Class类模板,因此主要exercise方法不释放锁,无论延迟多久,都不会执行study方法,因此会先输出跑步!

7.第七个问题

7.一个静态同步方法,一个普通的同步方法,只定义一个对象,先输出跑步还是考研?

package com.lock8;
import java.util.concurrent.TimeUnit;

public class Test4 {
    public static void main(String[] args) {
        People3 people= new People3();
        new Thread(()->{
            people.exercise();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            people.study();
        },"B").start();
    }
}

class People3{
    //静态同步方法
    public static synchronized void exercise(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("跑步");
    }
    //同步方法
    public synchronized void study(){
        System.out.println("考研");
    }
    public void exam(){
        System.out.println("考试");
    }
}

结果展示:
在这里插入图片描述
原因分析:
此种情况,普通的同步方法(synchronized)锁的是方法的调用者,而静态同步方法(static synchronized )锁的是Class类模板,即两个方法锁的不是同一个对象,所以后面调用的方法不需要去等待前一个延迟额锁,及时exercise方法不释放锁,我也可以去执行study方法,因此会先输出考研!
简单来说:如果两个锁上锁的是同一个对象,则必须要等到一个方法执行结束才能执行下一个方法,而当两个锁上锁的不是同一个对象,则当一个方法发生执行延迟时,就可以先执行下一个方法!

8.第八个问题

8.一个静态同步方法,一个普通的同步方法,定义两个对象,先输出跑步还是考研?

package com.lock8;
import java.util.concurrent.TimeUnit;

public class Test4 {
    public static void main(String[] args) {
        People3 people1= new People3();
        People3 people2= new People3();
        new Thread(()->{
            people1.exercise();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            people2.study();
        },"B").start();
    }
}

class People3{
    //静态同步方法
    public static synchronized void exercise(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("跑步");
    }
    //普通同步方法
    public synchronized void study(){
        System.out.println("考研");
    }
    public void exam(){
        System.out.println("考试");
    }
}

结果展示:
在这里插入图片描述
原因分析:
此时一个普通同步方法,锁的是方法的调用者一个静态同步方法,锁的是Class类模板,即表示二者上锁的不是同一个对象,所以不用等待exercise方法执行结束再去执行study方法,因此也会先输出考研!

小结

我们可以采用synchronized 给方法上锁,即表示方法是普通同步锁,使用synchronized 上锁的是方法的调用者;
我们也可以采用static synchronized 给方法上锁,表示方法是静态的同步方法锁,使用static synchronized 上锁锁的是资源类的Class类模板,而Class类模板是全局唯一;
规律:如果上锁的是同一个对象,则执行会按照先后顺序依次执行,只有前面的方法执行结束,才能执行后面的方法;如果上锁的不是同一个对象,首先也会按照先后顺序执行,一旦执行的过程中发现有sleep方法,即延迟,有CPU的空闲,就会执行下面与它不是同一个锁的方法,提高CPU执行的效率!

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

散一世繁华,颠半世琉璃

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

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

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

打赏作者

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

抵扣说明:

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

余额充值