JUC学习笔记(二):八锁现象

八锁现象主要是指 八种锁的情况,掌握了这八种情况我们以后碰到锁就能明白锁锁的是谁了

在了解八锁之前,我们需要先了解什么是锁:

        锁是一种将资源私有化的的方式,在java中就是让某个类、变量、方法在同一个时刻只能被一个线程使用,保证数据的安全性,只要当使用锁的线程释放了这个锁,其它线程才能获取使用。

(1)让两个方法都加上同步锁

package com.lai.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @author lly
 * @version 1.0
 * @date 2021/12/8 13:55
 */
public class Test {

    public static void main(String[] args) throws InterruptedException {

        Phone phone = new Phone();

        new Thread(()->phone.sendMeg(),"A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->phone.call(),"B").start();
    }
}

class Phone{
    //锁的对象是方法的调用者
    public synchronized void sendMeg(){System.out.println("sendMeg");}
    public synchronized void call(){ System.out.println("call");}
}

输出结果是:

sendMeg
call

很多人可能以为输出结果是这样是因为程序从上往下实行,所以先打印出sendMeg,那么我们继续往下看。

(2)让第一个方法休眠一段时间

package com.lai.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @author lly
 * @version 1.0
 * @date 2021/12/8 13:55
 */
public class Test {

    public static void main(String[] args) throws InterruptedException {

        Phone phone = new Phone();

        new Thread(()->phone.sendMeg(),"A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->phone.call(),"B").start();
    }
}

class Phone{
    //锁的对象是方法的调用者
    public synchronized void sendMeg(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMeg");
    }
    public synchronized void call(){ System.out.println("call");}
}

输出结果:

sendMeg
call

此时sendMeg方法中休眠了两秒,如果按第一种猜测是不是应该先输出call呢,因为方法一休眠了两秒,应该晚于call打印,但此时输出结果依然是sendMeg。

原因:

之所以先输出sendMeg,是因为上面的锁锁的是方法的调用者,即锁的是phone,并且两个方法都加了同步锁,因为线程A先获取到了锁,所以只能等线程A执行完释放了锁其他线程才能继续往下执行。

(3)增加一个普通的方法

package com.lai.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @author lly
 * @version 1.0
 * @date 2021/12/8 13:55
 */
public class Test2 {

    public static void main(String[] args) throws InterruptedException {
        Phone2 phone = new Phone2();
        new Thread(()->phone.sendMeg(),"A").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(()->phone.hello(),"B").start();

    }
}

class Phone2{
    //锁的对象是方法的调用者
    public synchronized void sendMeg(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"=>sendMeg");
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"=>call");
    }

    public void hello(){
        System.out.println("hello");
    }
}

打印结果:

hello
A=>sendMeg

原因:

此时因为hello方法没有加同步锁,所以不用获取到锁就可以执行,又因为sendMeg方法休眠了两秒,所以hello的打印必然在sendMeg之前。

(4)创建两个不同的实例

package com.lai.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @author lly
 * @version 1.0
 * @date 2021/12/8 13:55
 */
public class Test2 {

    public static void main(String[] args) throws InterruptedException {
        Phone2 phone = new Phone2();
        Phone2 phone2 = new Phone2();
        new Thread(()->phone.sendMeg(),"A").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(()->phone2.call(),"B").start();

    }
}

class Phone2{
    //锁的对象是方法的调用者
    public synchronized void sendMeg(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"=>sendMeg");
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"=>call");
    }

    public void hello(){
        System.out.println("hello");
    }
}

输出结果:

B=>call
A=>sendMeg
此时输出结果跟测试(2)不一样

原因:

正是因为synchronized锁的是方法的调用者,而调用两个方法的又是两个不同的实例,所以执行方法call的时候不用等待sendMeg方法释放锁就可以执行,又因为sendMeg方法中休眠了3秒,所以call先于sendMeg打印出来

(5)将方法改为静态同步方法

package com.lai.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @author lly
 * @version 1.0
 * @date 2021/12/8 14:14
 */
public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Phone3 phone = new Phone3();

        new Thread(()->phone.sendMeg(),"A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->phone.call(),"B").start();
    }

}

class Phone3{
    //锁的对象是方法的调用者
    //static,类一加载就有了,锁的是class
    public static synchronized void sendMeg(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMeg");
    }
    public static synchronized void call(){
        System.out.println("call");
    }
}

输出结果:

sendMeg
call

咋一看上去不是跟(2)中的原理一样吗,都是同一个对象实例调用的方法,那么我们继续往下看

(6)创建两个不同的实例

package com.lai.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @author lly
 * @version 1.0
 * @date 2021/12/8 14:14
 */
public class Test3 {
    public static void main(String[] args) throws InterruptedException {

        Phone3 phone = new Phone3();
        Phone3 phone2 = new Phone3();


        new Thread(()->phone.sendMeg(),"A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->phone2.call(),"B").start();
    }

}

class Phone3{
    //锁的对象是方法的调用者
    //static,类一加载就有了,锁的是class
    public static synchronized void sendMeg(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMeg");
    }
    public static synchronized void call(){
        System.out.println("call");
    }
}

输出结果:

sendMeg
call

此时输出结果又跟(4)中的不一样

原因:

方法中加了static关键字后在类加载的时候就有了,所以锁的是class,因为两个方法都锁了class,即Phone,所以还是谁先获取到锁就谁先执行。

(7)将一个静态同步方法变为普通同步方法,一个实例

package com.lai.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @author lly
 * @version 1.0
 * @date 2021/12/8 14:22
 */
public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        
        Phone4 phone = new Phone4();
     

        new Thread(()->phone.sendMeg(),"A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->phone.call(),"B").start();
    }

}

class Phone4{
    
    //static,类一加载就有了,锁的是class类模板
    public static synchronized void sendMeg(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMeg");
    }
    //锁的对象是方法的调用者
    public synchronized void call(){
        System.out.println("call");
    }
}

输出结果:

call
sendMeg

原因:

加了static关键字的方法锁的是class类模板

而普通的同步方法锁的是调用者,也就是实例,所以用的不是同一把锁,call先输出

(8)再创建一个Phone的实例

package com.lai.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @author lly
 * @version 1.0
 * @date 2021/12/8 14:22
 */
public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        Phone4 phone = new Phone4();
        Phone4 phone2 = new Phone4();

        new Thread(()->phone.sendMeg(),"A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->phone2.call(),"B").start();
    }

}

class Phone4{
    //锁的对象是方法的调用者
    //static,类一加载就有了,锁的是class
    public static synchronized void sendMeg(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMeg");
    }
    public synchronized void call(){
        System.out.println("call");
    }
}

输出结果:

call
sendMeg

此次输出结果的原因与(7)类似,因为两个方法所用的不是同一把锁,所以即使再创建一个实例,也还是互不影响

总结

到此为止关于8锁的问题已经分享完毕啦,其实八锁就是关于锁的在不同情况下输出结果的问题,搞懂了锁的是对象还是类模板的话我们就能清晰地知道输出以及运行的情况是怎样的。

要是有什么疑问的话欢迎小伙伴们在下方评论区留言哦~

上一篇:5G来了,普通人看热闹,程序员看颠覆


下一篇:python魔术方法__call__的研究