线程八锁现象的理解

本文详细探讨了Java多线程中关于锁的八个问题,包括正常情况下的线程执行顺序、线程睡眠后的执行顺序、同步方法与普通方法的区别、不同对象的执行顺序、静态同步方法的特性、两个对象与两个静态同步方法的执行情况、静态同步方法与普通同步方法的执行顺序以及两个对象、一个静态同步方法和一个普通同步方法的执行顺序。通过实例分析,揭示了synchronized锁的对象和执行逻辑。
摘要由CSDN通过智能技术生成

八锁即锁的八个问题

一、正常情况下线程的执行顺序

package demo_02;
// 八锁问题
// 正常线程执行情况

public class Test_01 {
    public static void main(String[] args) {
        PrintMessage pm = new PrintMessage();
        new Thread(()->{pm.printA();},"A").start();
        new Thread(()->{pm.printB();},"B").start();
    }
}
class PrintMessage{
    public synchronized void printA(){
        System.out.println(Thread.currentThread().getName()+" 输出了 => " + "A");
    }
    public synchronized void printB(){
        System.out.println(Thread.currentThread().getName()+" 输出了 => " + "B");
    }
}

执行结果:在这里插入图片描述

可以看到这里是先执行的printA,在执行的printB,可是原因是什么呢?因为先调用的printA方法,再调用的printB方法吗?答案是否定的

二、睡眠A线程3秒,线程的执行顺序

有人可能会觉得是因为先调用的printA方法再调用的printB方法的问题,因此我们让printA方法睡三秒

package demo_02;
// 八锁问题


import java.util.concurrent.TimeUnit;

public class Test_01 {
    public static void main(String[] args) {
        PrintMessage pm = new PrintMessage();
        new Thread(()->{
            try {
                pm.printA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{pm.printB();},"B").start();
    }
}
class PrintMessage{
    public synchronized void printA() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName()+" 输出了 => " + "A");
    }
    public synchronized void printB(){
        System.out.println(Thread.currentThread().getName()+" 输出了 => " + "B");
    }
}

执行结果:在这里插入图片描述

不管执行多少次都是先打印A再打印B,注意这里并不是因为先调用的printA方法,再调用printB方法的问题,这样理解根本没有理解到关键。
这里出现先打印A再打印B的原因是因为大家没有理解synchronized锁,锁的是什么?这个地方锁的是PrintMessage这个类的调用者pm对象,所以在A线程屌用printA方法时,会对pm对象加锁,B线程要等待A线程释放pm锁之后才可以获得pm对象。

三、普通方法和synchronized方法执行问题

package demo_02;
// 八锁问题


import java.util.concurrent.TimeUnit;

public class Test_01 {
    public static void main(String[] args) {
        PrintMessage pm = new PrintMessage();
        new Thread(()->{
            try {
                pm.printA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{pm.printC();},"C").start();
    }
}
class PrintMessage{
    public synchronized void printA() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName()+" 输出了 => " + "A");
    }
    public void printC(){
        System.out.println(Thread.currentThread().getName()+" 输出了 => " + "C");
    }
}

执行结果:在这里插入图片描述

这里为了让结果看起来更清晰,我将A线程睡了三秒。这里先打印C的原因是在A线程拿到了pm对象的锁的时候,C线程可以直接对pm对象进行操作,因为printC方法并不是同步方法,不需要进行同步操作,A线程有没有锁对C线程调用printC方法都没有影响。

四、两个线程不同的对象执行顺序

为了获取观察效果,在这里同样让线程A睡3秒

package demo_02;
// 八锁问题


import java.util.concurrent.TimeUnit;

public class Test_01 {
    public static void main(String[] args) {
        PrintMessage pm1 = new PrintMessage();
        PrintMessage pm2 = new PrintMessage();
        new Thread(()->{
            try {
                pm1.printA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{pm2.printB();},"B").start();
    }
}
class PrintMessage{
    public synchronized void printA() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName()+" 输出了 => " + "A");
    }
    public synchronized void printB(){
        System.out.println(Thread.currentThread().getName()+" 输出了 => " + "B");
    }
}

执行结果:在这里插入图片描述

这里虽然是两个同步方法,但是synchronized锁的对象不同,因此线程A和线程B调用的printA和printB方法分别是不对对象的方法,两个线程并不冲突,因为A线程睡眠的3秒,所以先打印B再打印A。

五、静态同步方法的执行顺序

将同步方法更改为static静态方法,看一看执行顺序有什么不同。
static修饰的方法会在类第一次载入的时候被执行。

package demo_02;
// 八锁问题


import java.util.concurrent.TimeUnit;

public class Test_01 {
    public static void main(String[] args) {
        PrintMessage pm = new PrintMessage();
        new Thread(()->{
            try {
                pm.printA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{pm.printB();},"B").start();
    }
}
class PrintMessage{
    public static synchronized void printA() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName()+" 输出了 => " + "A");
    }
    public static synchronized void printB(){
        System.out.println(Thread.currentThread().getName()+" 输出了 => " + "B");
    }
}

执行结果:
在这里插入图片描述

这里虽然和前面的正常情况的输出结果一样,但是其内在原理却不相同。首先我们需要理解一下static关键字,static声明的静态方法,会在类被加载的时候就执行。可以理解成static方法在对象被new的时候就已经执行,执行后的方法会放在class文件中(java文件编译后产生的class文件),class又被称为该类的模板,一个类在运行过程只只会有一个模板这里synchronized锁,锁的对象是一个模板,而不是一个类对象,虽然输出结果一样但是锁的对象并不相同。
可能很多同学还不是太懂于是就有了第六个锁的问题

六、两个对象,两个静态同步方法

如果之前大家可能还没有想通,我们创建两个PrintMessage的对象,分别调用打印方法,按道理如果A线程睡眠3秒,那B线程就会先打印,然后在打印A线程的输出方法,但是如果是静态同步方法呢?

package demo_02;
// 八锁问题


import java.util.concurrent.TimeUnit;

public class Test_01 {
    public static void main(String[] args) {
        PrintMessage pm1 = new PrintMessage();
        PrintMessage pm2 = new PrintMessage();
        new Thread(()->{
            try {
                pm1.printA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{pm2.printB();},"B").start();
    }
}
class PrintMessage{
    public static synchronized void printA() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName()+" 输出了 => " + "A");
    }
    public static synchronized void printB(){
        System.out.println(Thread.currentThread().getName()+" 输出了 => " + "B");
    }
}

执行结果:
在这里插入图片描述

七、一个静态同步方和一个普通方法,一个对象的执行顺序

package demo_02;
// 八锁问题


import java.util.concurrent.TimeUnit;

public class Test_01 {
    public static void main(String[] args) {
        PrintMessage pm = new PrintMessage();
        new Thread(()->{
            try {
                pm.printA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{pm.printB();},"B").start();
    }
}
class PrintMessage{
    public static synchronized void printA() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName()+" 输出了 => " + "A");
    }
    public  void printB(){
        System.out.println(Thread.currentThread().getName()+" 输出了 => " + "B");
    }
}

执行结果:

相信大家如果认真看了前面的问题,就知道这个问题的原因了,因为普通方法并不会获取锁资源,所以线程B的普通方法可以直接执行,线程A睡眠3秒后再执行。

八、两个对象,一个静态同步方法,一个普通同步方法,执行顺序

这个地方的关键是要明白锁的对象锁的是什么?

package demo_02;
// 八锁问题


import java.util.concurrent.TimeUnit;

public class Test_01 {
    public static void main(String[] args) {
        PrintMessage pm1 = new PrintMessage();
        PrintMessage pm2 = new PrintMessage();
        new Thread(()->{
            try {
                pm1.printA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{pm2.printB();},"B").start();
    }
}
class PrintMessage{
    public static synchronized void printA() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName()+" 输出了 => " + "A");
    }
    public synchronized void printB(){
        System.out.println(Thread.currentThread().getName()+" 输出了 => " + "B");
    }
}

执行结果:
在这里插入图片描述

首先静态同步方法锁的是类模板(class文件),而普通同步方法锁的是PrintMessage的对象,所以两个锁的对象是不同的,两个锁并不冲突,自己锁自己的,线程A睡眠3秒后输出,线程B正常输出,所以先输出B线程再输出A线程。

该部分内容是笔者参考狂神的学习视频,做的一些总结以及自己的思考,如果有纰漏的地方,希望大家多多指出,一起学习!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值