八锁现象的介绍及代码分析

好的,前文我们简单的讲述了一下synchronized 锁的三种应用方式,他们是:

  • 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
  • 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
  • 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

好的我们知道了有这三种方式,但是他们三种不同的应用方式又有着什么不一样的区别呢,难道说还是只是位置不一样,其他作用都是一样的,那肯定不是的啦,他们的锁对象都是不一样的,下面我们就会讲一下八种不同的情况,也就是八锁现象。

1.标准访问,多个线程使用同一把锁

多个线程操作同一个对象,那么他们的锁就是同一把锁,也就是一个对象,调用多个同步方法,执行的结果就是:先调用的先执行,按照顺序来执行

public class lock01 {
    public static void main(String[] args) {
        Mobile mobile = new Mobile(); //创建一个手机类对象
        //开启两个线程,执行两个不同的同步方法
        //那么会是哪一个方法先执行呢 ? 打电话 还是  发短信?
        new Thread(()->mobile.call()).start();
        new Thread(()->mobile.msg()).start();
        // 结果当然是打电话先执行啦,因为他先被调用
    }
}

class Mobile{
    //手机类里面有两个方法,打电话,发短信
    public synchronized void call(){ //synchronized 是加在方法上的,是同步方法
        System.out.println("我在给女神打电话啦。");
    }

    public synchronized void msg(){
        System.out.println("我在给女神发短信啦");
    }
}

第一个锁的结果
好的,从上面的结果我们可以看出来是打电话先执行的,如果你将两个线程的位置换一下,那么结果也会发生变化的。但是,我们还是可以知道,多个线程操作同一个对象的同步方法时,先调用的先执行,更准确的描述,就是先拿到锁的人先执行。 为什么呢,因为synchroniezd锁是方法的调用者,而我们操作的是同一个对象的同步方法,在上面的例子中,锁对象就是mobile。第一个方法执行的时候已经得到这个锁了,这个时候其他线程执行自己的方法时,由于没有锁,就处于阻塞状态啦,就会等待,直到获取到锁,才会继续执行。

2.多个线程使用同一把锁,但是其中一个线程阻塞

他的执行结果是:先拿到锁的先执行

public class lock02 {
    public static void main(String[] args) throws InterruptedException {
        Mobile02 mobile = new Mobile02(); //创建一个手机类对象
        //开启两个线程,执行两个不同的同步方法,并且第一个线程先沉睡一秒,在执行同步方法
        //那么会是哪一个方法先执行呢 ? 打电话 还是  发短信?
        new Thread(()->{
            try {
                System.out.println("我进入方法啦,但是没有拿到锁");
                TimeUnit.SECONDS.sleep(1);   //这个时候我们先睡眠,注意,我们没有执行方法,这个时候是没有拿到锁的
                System.out.println("我要休息1秒种");
                mobile.call();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->mobile.msg()).start();
    }
}

class Mobile02{
    //手机类里面有两个方法,打电话,发短信
    public synchronized void call() throws InterruptedException { //synchronized 是加在方法上的,是同步方法
        System.out.println("我在给女神打电话啦。");
            TimeUnit.SECONDS.sleep(3);  //睡3秒种
            System.out.println("睡3秒");
    }
    public synchronized void msg(){
        try {
            System.out.println("我在给女神发短信啦");
            TimeUnit.SECONDS.sleep(3);  //拿到锁后睡三秒
            System.out.println("我也来睡三秒钟");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

lock2的运行结果

好的,从上面的运行结果来看,发现想得好像和先前的不一样啦,完啦,这个博主自己写博客都翻车了,真垃圾。为什么会这样呢,不是先调用的先执行吗,按照顺序来执行,明明call方法代码块在上面,为什么先执行了msg方法呢,我们来看一下代码,我们首先还是进入了call方法,输出语句,但是这个时候,我们让他睡眠一秒钟,这个时候我们的call方法还没有被执行,也就是说 mobile对象,是没有被任何人占有的,这个时候线程2就得到了CPU的运行时间,发现锁对象存在,那么就拿到了锁,先执行,输出发短信,然后线程2也开始睡眠3秒种,这个时候线程1就拿到了CPU的时间片,执行自己的语句,可是我们发现call方法是没有被执行的,为什么呢,因为锁是被线程2给拿到了,我没有锁对象,我只能等,后面的结果就很清晰了,线程2释放锁资源,线程1拿到,然后执行完毕在释放。
总结: 我们从上面的例子可以发现,多个线程之间操作同一个锁对象的时候,执行的顺序是看谁先拿到锁,也就是谁先被调用,在拿到锁之后,即使我是处于阻塞的状态,锁依然是我的,但是我在没拿到锁之前,我就睡眠,那么调用的顺序就不在一定和代码的顺序相同啦,当然,上面的例子是可以改的,我们可以把线程1的休眠去掉,或者换一个位置,那么结果就会变得。来个小鸡汤:生活也是这样呀,不努力,只知道休息得话,想要的都会被别人拿走得。

3.调用同一个对象得普通同步方法和普通方法

public class lock03 {
    public static void main(String[] args) throws InterruptedException {
        Mobile03 mobile = new Mobile03(); //创建一个手机类对象
        //那么会是哪一个方法先执行呢 ? 打电话 还是  发短信?
        new Thread(() -> mobile.call()).start();
        new Thread(() -> mobile.msg()).start();
    }
}

class Mobile03 {
    //手机类里面有两个方法,打电话,发短信
    public synchronized void call() {  //同步方法

        try {
            System.out.println("我在给女神打电话啦。");
            System.out.println("我要休息5秒种");
            TimeUnit.SECONDS.sleep(5);
            System.out.println("休息完毕");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public  void msg() {  //普通方法,没有锁,不受同步方法得影响
        System.out.println("我在给女神发短信啦");
    }
}

lock03得运行结果
从上图可以看出,同步方法在获得锁得时候,然后睡眠,普通方法还是会执行得,所以说,普通方法和同步方法执行是没有关系得,只是看代码得位置而已,他们不会因为锁得问题而发生阻塞,因为普通方法是没有锁得,即mobile锁被其他线程占用了,也不影响我自己线程普通方法得执行。

4.两个对象,两个不同得同步方法

public class lock04 {
    public static void main(String[] args) throws InterruptedException {
        Mobile04 mobile1 = new Mobile04();
        Mobile04 mobile2 = new Mobile04(); //创建两个手机类对象
        //开启两个线程,执行两个不同的同步方法
        //那么会是哪一个方法先执行呢 ? 打电话 还是  发短信?
        new Thread(() -> mobile1.call()).start();
        new Thread(() -> mobile2.msg()).start();
    }
}

class Mobile04 {
    //手机类里面有两个方法,打电话,发短信
    public synchronized void call() {  //同步方法

        try {
            System.out.println("我在给女神打电话啦。");
            System.out.println("我要休息2秒种");
            TimeUnit.SECONDS.sleep(2);
            System.out.println("2s休息完毕");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void msg() {  //同步方法
        try {
            System.out.println("我在给女神发短信啦。");
            System.out.println("我要休息3秒种");
            TimeUnit.SECONDS.sleep(3);
            System.out.println("3s休息完毕");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

lock04运行结果
我们可以从运行结构里面发现。两个方法互不影响得,你将方法锁住了对我是没有任何影响得,为什么呢。因为这个时候我们调用得方法是两个不同得对象,就是两把锁,就相当于我们有两座房子A和B,每个房子只能进去一个人,这个时候A房子进去人了,就不允许其他人进来了,可是这个时候我们是两个对象,就是两个房子,我第二个人我进去得是B房子,B房子是空的,有没有人。我在A房子里面进卧室呀,去厨房等一系列得操作,是怎么会影响到B房子里面得人呢。也就是说,调用不同对象得同步方法,是互相不影响得,因为是两把锁。

5.一个对象,两个静态方法

public class lock05 {
    public static void main(String[] args) throws InterruptedException {
        Mobile05 mobile = new Mobile05();
        //开启两个线程,执行两个不同的同步方法
        //那么会是哪一个方法先执行呢 ? 打电话 还是  发短信?
        new Thread(() -> mobile.call()).start();
        new Thread(() -> mobile.msg()).start();
    }
}

class Mobile05 {
    //手机类里面有两个方法,打电话,发短信
    public static synchronized void call() {  //静态同步方法

        try {
            System.out.println("我在给女神打电话啦。");
            System.out.println("我要休息1秒种--打电话");
            TimeUnit.SECONDS.sleep(1);
            System.out.println("1s休息完毕--打电话");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static synchronized void msg() { //静态同步方法
        try {
            System.out.println("我在给女神发短信啦。");
            System.out.println("我要休息1秒种--发短信");
            TimeUnit.SECONDS.sleep(1);
            System.out.println("1s休息完毕--发短信");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

lock05运行结果
这个结果我想应该都想出来了,和上面得那个一个对象,两个同步方法一样得结果嘛,对的,是一样得,但是原因是不一样得,他们锁得对象变了,同步方法锁对象是方法得调用者,但是静态方法锁对象是该对象得class文件 ,也就是他的类,因为我们是static静态方法,就是说类在初始化得时候,该方法就已经被加载了,是属于类得,每一个被这个类创建得对象是共享一份得,所以说,也就是谁先拿到这个锁对象,谁就先执行。

6.两个对象,两个静态方法

public class lock06 {
    public static void main(String[] args) throws InterruptedException {
        Mobile06 mobile01 = new Mobile06();
        Mobile06 mobile02 = new Mobile06();
        //开启两个线程,执行两个不同的同步方法
        //那么会是哪一个方法先执行呢 ? 打电话 还是  发短信?
        new Thread(() -> mobile01.call()).start();
        new Thread(() -> mobile02.msg()).start();
    }
}

class Mobile06 {
    //手机类里面有两个方法,打电话,发短信
    public static synchronized void call() {  //静态同步方法

        try {
            System.out.println("我在给女神打电话啦。");
            System.out.println("我要休息1秒种--打电话");
            TimeUnit.SECONDS.sleep(1);
            System.out.println("1s休息完毕--打电话");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static synchronized void msg() { //静态同步方法
        try {
            System.out.println("我在给女神发短信啦。");
            System.out.println("我要休息1秒种--发短信");
            TimeUnit.SECONDS.sleep(1);
            System.out.println("1s休息完毕--发短信");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

lock06运行结果
这个时候我们是两个对象,两个不同得静态方法,结果却和上面是一样得,为啥子。因为我们得锁是他的class对象,这个一个类只有一份得,所以对于静态方法来说,就算是十个线程,调用十个不同得静态方法,也都是要排队得,看谁那个这个唯一的锁,谁拿到谁执行。

7.一个对象,一个静态同步方法,一个普通同步方法

public class lock07 {
    public static void main(String[] args) throws InterruptedException {
        Mobile07 mobile = new Mobile07();
        //开启两个线程,执行两个不同的同步方法
        //那么会是哪一个方法先执行呢 ? 打电话 还是  发短信?
        new Thread(() -> mobile.call()).start();
        new Thread(() -> mobile.msg()).start();
    }
}

class Mobile07 {
    //手机类里面有两个方法,打电话,发短信
    public static synchronized void call() {  //静态同步方法

        try {
            System.out.println("我在给女神打电话啦。");
            System.out.println("我要休息1秒种--打电话");
            TimeUnit.SECONDS.sleep(1);
            System.out.println("1s休息完毕--打电话");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public  synchronized void msg() { //同步方法
        try {
            System.out.println("我在给女神发短信啦。");
            System.out.println("我要休息1秒种--发短信");
            TimeUnit.SECONDS.sleep(1);
            System.out.println("1s休息完毕--发短信");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

lock07运行结果
从运行结果可以看出来,他们并没有和上面的结果一样,两个方法之间互不影响,虽然你得到了锁,并且没有执行完毕,但是我仍然可以运行,这是为什么呢。因为 他们是两个不同的锁,静态同步方法的锁对象是对应的class模板,但是普通同步方法他的锁对象却是方法的调用者,两个人的锁对象都不一样,肯定是互相不影响的啦。

8.两个对象,一个静态方法,一个同步方法

public class lock08 {
    public static void main(String[] args) throws InterruptedException {
        Mobile08 mobile01 = new Mobile08();
        Mobile08 mobile02 = new Mobile08();
        //开启两个线程,执行两个不同的同步方法
        //那么会是哪一个方法先执行呢 ? 打电话 还是  发短信?
        new Thread(() -> mobile01.call()).start();
        new Thread(() -> mobile02.msg()).start();
    }
}

class Mobile08 {
    //手机类里面有两个方法,打电话,发短信
    public static synchronized void call() {  //静态同步方法

        try {
            System.out.println("我在给女神打电话啦。");
            System.out.println("我要休息1秒种--打电话");
            TimeUnit.SECONDS.sleep(1);
            System.out.println("1s休息完毕--打电话");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public  synchronized void msg() { //同步方法
        try {
            System.out.println("我在给女神发短信啦。");
            System.out.println("我要休息1秒种--发短信");
            TimeUnit.SECONDS.sleep(1);
            System.out.println("1s休息完毕--发短信");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

lock08运行结果
这个结果就和上面的差不多啦,因为锁的是两个不同东西,所以绝对是互相不影响的。

小结

锁的情况结果
一个对象,两个同步方法一个锁,谁先拿到锁,谁就先执行,一般是按照顺序执行
一个对象,两个同步方法,有阻塞一个锁,谁先拿到锁,谁就先执行,阻塞依然占有锁,其他线程等待锁资源
一个对象,一个同步方法,一个普通方法互不影响,一个有锁,一个没有锁
两个对象,两个同步方法互不影响,因为是两个锁对象
一个对象,两个静态方法谁先拿到锁,谁先执行,静态方法的锁对象是class模板,只有一个
两个对象,两个静态方法谁先拿到锁,谁先执行,静态方法的锁对象是class模板,只有一个
一个对象,一个静态方法,一个同步方法互不影响,两个不一样的锁,一个是class模板,一个是方法的调用者
两个对象,一个静态方法,一个同步方法互不影响,两个不一样的锁,一个是class模板,一个是方法的调用者

闲聊

好吧,我当时以为写这个八锁现象两个小时就可以的,结果我早上九点开始,写了快四个小时,害,我还是太高估自己了,写完这个发现我对这个八锁的理解又深了一步,哈哈哈,写的不是很好,哪里有问题的话,有错误,希望大佬们指点一下,小的好去慢慢改正,嘻嘻。

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值