拓展8锁问题

原问题

8锁问题探讨的是在多线程环境中,各线程访问资源的先后顺序。
具体问题为开启两个线程,其中线程2比线程1慢开启,线程1先进入了资源类的sendSms,然后延迟打印“发短信”,根据不同的条件,判断最终是先打印了“发短信”,还是“打电话”。

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

        Phone1 d = new Phone1();
        new Thread(()->{
            d.sendSms();
        }).start();
        
        //加大第一个匿名对象先执行sendSms方法的概率
        Thread.sleep(100);
        
        new Thread(()->{
            d.call();
        }).start();
    }
}

class Phone1{

    public void sendSms() {
        try {
            //延长打印“发短信”的时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public void call(){
        System.out.println("打电话");
    }
}

其实,通过查看这8个问题的条件,可以知道他们是通过改变几个条件来产生不同的锁问题的,分别是:

  1. 是否为静态方法
  2. 是否有Synchronized修饰
  3. 实例化一个或是两个资源类对象

而这3个条件可以用枚举法列举出所有的情况:
1、是否为静态方法:
因为有两个方法,所以可能的情况是:两个都为静态方法、两个都为非静态方法、一个静态方法一个非静态方法,共3种。
2、是否有Synchronized修饰:
两个方法都有Synchronized修饰、两个方法都没有Synchronized修饰、一个有修饰一个没有修饰,共3种。
3、实例化一个或是两个资源类对象:
实例化了一个对象、实例化了两个对象,共2种。
注意:

  • 因为这个问题要出现锁,如果没有锁的话就没有讨论的意义了,所以在第二个条件中至少要有一个方法有被Synchronized修饰,所以第二个条件只能有两种情况:两个方法都有Synchronized修饰、一个有修饰一个没有修饰。
  • 条件1的一个静态方法一个非静态方法和条件2的一个有Synchronized修饰一个没有Synchronized修饰的方法有两种可能的组合情况。

因此,总共需要探讨的情况有3×2×2+2=14种。

关注点

在探讨不同线程执行资源类方法的先后顺序中,我们需要了解:
1、当线程执行了资源类的有Synchronized修饰的方法时,它会拿到一个锁,这个锁与该方法是否是静态有关:

  • 如果该方法是静态方法,则拿到的是类锁
  • 如果该方法是非静态方法,则拿到的是对象锁

2、拿到了锁的线程只有在该方法正常执行结束后才会释放锁,之后其他线程才有机会能拿到锁

1、两个都是静态、Synchronized修饰的方法,一个资源类对象
public class LockTest1 {
    public static void main(String[] args) throws InterruptedException {

        Phone1 d = new Phone1();
        new Thread(()->{
            d.sendSms();
        }).start();

        //加大第一个匿名对象先执行sendSms方法的概率
        Thread.sleep(100);

        new Thread(()->{
            d.call();
        }).start();
    }
}

class Phone1{

    public static synchronized void sendSms() {
        try {
            //延长打印“发短信”的时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public static synchronized void call(){
        System.out.println("打电话");
    }
}

结果:

发短信
打电话

资源类的两个方法都有static、Synchronized修饰,所以线程执行任一个方法时都会拿到类锁,而线程一先进入sendSms方法,所以需要等待sendSms执行完毕后才能释放类锁。

2、两个都是静态、Synchronized修饰的方法,两个资源类对象
public class LockTest2 {
    public static void main(String[] args) throws InterruptedException {

        Phone2 d1 = new Phone2();
        Phone2 d2 = new Phone2();

        new Thread(()->{
            d1.sendSms();
        }).start();

        //加大第一个匿名对象先执行sendSms方法的概率
        Thread.sleep(100);

        new Thread(()->{
            d2.call();
        }).start();
    }
}

class Phone2{

    public static synchronized void sendSms() {
        try {
            //延长打印“发短信”的时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public static synchronized void call(){
        System.out.println("打电话");
    }
}

结果:

发短信
打电话

与第1种情况类似,因为进入两个方法拿到的都是类锁,所以结果打印的先后顺序只与这个类有关,与是这个类的哪个对象调用的方法无关。

3、两个都是非静态、Synchronized修饰的方法,一个资源类对象
public class LockTest3 {
    public static void main(String[] args) throws InterruptedException {

        Phone3 d = new Phone3();
        new Thread(()->{
            d.sendSms();
        }).start();

        //加大第一个匿名对象先执行sendSms方法的概率
        Thread.sleep(100);

        new Thread(()->{
            d.call();
        }).start();
    }
}

class Phone3{

    public synchronized void sendSms() {
        try {
            //延长打印“发短信”的时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

结果

发短信
打电话

资源类的两个方法是非静态的,并且都有Synchronized修饰,所以线程执行任一个方法后都会拿到对象锁,而两个线程执行的都是同一个资源类对象的方法,所以当线程一执行sendSms方法时拿到了对象锁,此时线程二必须等待线程一释放对象锁后,才有可能拿到对象锁。

4、两个都是非静态、Synchronized修饰的方法,两个资源类对象
public class LockTest4 {
    public static void main(String[] args) throws InterruptedException {

        Phone4 d1 = new Phone4();
        Phone4 d2 = new Phone4();

        new Thread(()->{
            d1.sendSms();
        }).start();

        //加大第一个匿名对象先执行sendSms方法的概率
        Thread.sleep(100);

        new Thread(()->{
            d2.call();
        }).start();
    }
}

class Phone4{

    public synchronized void sendSms() {
        try {
            //延长打印“发短信”的时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

结果:

打电话
发短信

与第3种情况类似,但由于是调用两个资源类对象的方法,所以两个线程拿到的对象锁是不同的,两者并不冲突,线程二不用等待,又sendSms方法打印“发短信”的时间较长,所以程序运行结果是先打印了“打电话”,再打印“打短信”。

5、一个静态、Synchronized修饰的方法,一个非静态、Synchronized修饰的方法,一个资源类对象
ew Phone5();
        new Thread(()->{
            d.sendSms();
        }).start();

        //加大第一个匿名对象先执行sendSms方法的概率
        Thread.sleep(100);

        new Thread(()->{
            d.call();
        }).start();
    }
}

class Phone5{

    public static synchronized void sendSms() {
        try {
            //延长打印“发短信”的时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

结果:

打电话
发短信

执行sendSms方法拿到了类锁,执行call拿到了对象锁,所以线程二不用等待,最终先打印了“打电话”。

6、一个静态、Synchronized修饰的方法,一个非静态、Synchronized修饰的方法,两个资源类对象
public class LockTest6 {
    public static void main(String[] args) throws InterruptedException {

        Phone6 d1 = new Phone6();
        Phone6 d2 = new Phone6();

        new Thread(()->{
            d1.sendSms();
        }).start();

        //加大第一个匿名对象先执行sendSms方法的概率
        Thread.sleep(100);

        new Thread(()->{
            d2.call();
        }).start();
    }
}

class Phone6{

    public static synchronized void sendSms() {
        try {
            //延长打印“发短信”的时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

结果:

打电话
发短信

与第5种情况类似,线程二不用等待,并且与是哪个对象调用的方法无关。

7、一个静态、Synchronized修饰的方法,一个静态、无Synchronized修饰的方法,一个资源类对象
public class LockTest7 {
    public static void main(String[] args) throws InterruptedException {

        Phone7 d = new Phone7();
        new Thread(()->{
            d.sendSms();
        }).start();

        //加大第一个匿名对象先执行sendSms方法的概率
        Thread.sleep(100);

        new Thread(()->{
            d.call();
        }).start();
    }
}

class Phone7{

    public static synchronized void sendSms() {
        try {
            //延长打印“发短信”的时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public static void call(){
        System.out.println("打电话");
    }
}

结果:

打电话
发短信

call方法没有Synchronized修饰,只是普通方法,所以线程二不用等待,直接执行call方法。

8、一个静态、Synchronized修饰的方法,一个静态、无Synchronized修饰的方法,两个资源类对象
public class LockTest8 {
    public static void main(String[] args) throws InterruptedException {

        Phone8 d1 = new Phone8();
        Phone8 d2 = new Phone8();

        new Thread(()->{
            d1.sendSms();
        }).start();

        //加大第一个匿名对象先执行sendSms方法的概率
        Thread.sleep(100);

        new Thread(()->{
            d2.call();
        }).start();
    }
}

class Phone8{

    public static synchronized void sendSms() {
        try {
            //延长打印“发短信”的时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public static void call(){
        System.out.println("打电话");
    }
}

结果:

打电话
发短信

与第7种情况类似。

9、一个非静态、Synchronized修饰的方法,一个非静态、无Synchronized修饰的方法,一个资源类对象
public class LockTest9 {
    public static void main(String[] args) throws InterruptedException {

        Phone9 d = new Phone9();
        new Thread(()->{
            d.sendSms();
        }).start();

        //加大第一个匿名对象先执行sendSms方法的概率
        Thread.sleep(100);

        new Thread(()->{
            d.call();
        }).start();
    }
}

class Phone9{

    public synchronized void sendSms() {
        try {
            //延长打印“发短信”的时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public void call(){
        System.out.println("打电话");
    }
}

结果:

打电话
发短信

sendSms方法有Synchronized修饰,所以执行他会拿到对象锁,而call方法只是普通的方法,所以线程二不用等待,直接执行call方法。

10、一个非静态、Synchronized修饰的方法,一个非静态、无Synchronized修饰的方法,两个资源类对象
public class LockTest10 {
    public static void main(String[] args) throws InterruptedException {

        Phone10 d1 = new Phone10();
        Phone10 d2 = new Phone10();

        new Thread(()->{
            d1.sendSms();
        }).start();

        //加大第一个匿名对象先执行sendSms方法的概率
        Thread.sleep(100);

        new Thread(()->{
            d2.call();
        }).start();
    }
}

class Phone10{

    public synchronized void sendSms() {
        try {
            //延长打印“发短信”的时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public void call(){
        System.out.println("打电话");
    }
}

结果:

打电话
发短信

与第9种情况类似。

11、一个静态、Synchronized修饰的方法,一个非静态、无Synchronized修饰的方法,一个资源类对象
public class LockTest11 {
    public static void main(String[] args) throws InterruptedException {

        Phone11 d = new Phone11();
        new Thread(()->{
            d.sendSms();
        }).start();

        //加大第一个匿名对象先执行sendSms方法的概率
        Thread.sleep(100);

        new Thread(()->{
            d.call();
        }).start();
    }
}

class Phone11{

    public static synchronized void sendSms() {
        try {
            //延长打印“发短信”的时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public void call(){
        System.out.println("打电话");
    }
}

结果:

打电话
发短信

call方法是普通方法,所以线程二不用等待,直接执行call方法。

12、一个静态、Synchronized修饰的方法,一个非静态、无Synchronized修饰的方法,两个资源类对象
public class LockTest12 {
    public static void main(String[] args) throws InterruptedException {

        Phone12 d1 = new Phone12();
        Phone12 d2 = new Phone12();

        new Thread(()->{
            d1.sendSms();
        }).start();

        //加大第一个匿名对象先执行sendSms方法的概率
        Thread.sleep(100);

        new Thread(()->{
            d2.call();
        }).start();
    }
}

class Phone12{

    public static synchronized void sendSms() {
        try {
            //延长打印“发短信”的时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public void call(){
        System.out.println("打电话");
    }
}

结果:

打电话
发短信

与第11种情况类似。

13、一个静态、无Synchronized修饰的方法,一个非静态、Synchronized修饰的方法,一个资源类对象
public class LockTest13 {
    public static void main(String[] args) throws InterruptedException {

        Phone13 d = new Phone13();
        new Thread(()->{
            d.sendSms();
        }).start();

        //加大第一个匿名对象先执行sendSms方法的概率
        Thread.sleep(100);

        new Thread(()->{
            d.call();
        }).start();
    }
}

class Phone13{

    public static void sendSms() {
        try {
            //延长打印“发短信”的时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

结果:

打电话
发短信

sendSms方法是普通的方法,而call方法有Synchronized修饰,所以两个线程不用相互等待,线程二会直接执行call方法。

14、一个静态、无Synchronized修饰的方法,一个非静态、Synchronized修饰的方法,两个资源类对象
public class LockTest14 {
    public static void main(String[] args) throws InterruptedException {

        Phone14 d1 = new Phone14();
        Phone14 d2 = new Phone14();

        new Thread(()->{
            d1.sendSms();
        }).start();

        //加大第一个匿名对象先执行sendSms方法的概率
        Thread.sleep(100);

        new Thread(()->{
            d2.call();
        }).start();
    }
}

class Phone14{

    public static void sendSms() {
        try {
            //延长打印“发短信”的时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

结果:

打电话
发短信

与第13种情况类似。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值