通俗易懂synchronized的底层原理

synchronized代码反编译

jvm基于进入和退出Monitor对象来实现方法同步和代码块同步,其中方法同步是使用ACC_SYNCHRONIZED 、代码块同步是使用monitorenter、monitorexit指令

  • synchronized是可重入的,所以不会自己把自己锁死
  • synchronized锁一旦被一个线程持有,其他试图获取该锁的线程将被阻塞(这也是synchronized最原始的实现,重量级锁的特点)。

对应的代码和反编译代码如下,使用javap -v xxx.class进行反编译

public class SynchronizedTest {
    public void get(){
        synchronized (this){        // 这个是同步代码块
            System.out.println("你好呀");
        }
    }
    public synchronized void f(){    //这个是同步方法
        System.out.println("Hello world");
    }

    public static synchronized void f2(){    //这个是同步方法
        System.out.println("Hello world");
    }

    public static void main(String[] args) {
    }

}

synchronized作用在代码块时,

它的底层是通过monitorenter、monitorexit指令来实现的。
请添加图片描述
monitorenter:

每个对象都是一个监视器锁(monitor),当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。**如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。**如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

monitorexit:

**执行monitorexit的线程必须是object所对应的monitor持有者。**指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权。

monitorexit指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异步退出释放锁

synchronized作用在同步方法

代码中的f和f2方法对应反编译中代码如下图:
请添加图片描述
方法的同步并没有通过 monitorenter 和 monitorexit 指令来完成,不过相对于普通方法,其常量池中多了 ACC_SYNCHRONIZED 标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。归根结底还是对monitor对象的争夺,只是同步方法是一种隐式的方式来实现。

面试题一、synchronized可以修饰静态方法和静态代码块吗?

参考答案
synchronized可以修饰静态方法,但不能修饰静态代码块。
当修饰静态方法时,监视器锁(monitor)便是对象的Class实例,因为Class数据存在于永久代,因此
静态方法锁相当于该类的一个全局锁。

面试题二、如何使用 synchronized

synchronized 关键字的使用方式主要有下面 3 种:

  1. 修饰实例方法
  2. 修饰静态方法
  3. 修饰代码块

1、修饰实例方法 (锁当前对象实例)

给当前对象实例加锁,进入同步代码前要获得 当前对象实例的锁

synchronized void method() {
    //业务代码
}

2、修饰静态方法 (锁当前类)

给当前类加锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当前 class 的锁
这是因为静态成员不属于任何一个实例对象,归整个类所有,不依赖于类的特定实例,被类的所有实例共享。

synchronized static void method() {
    //业务代码
}

静态 synchronized 方法和非静态 synchronized 方法之间的调用互斥么?不互斥!如果一个线程 A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。

3、修饰代码块 (锁指定对象/类)

对括号里指定的对象/类加锁:

  • synchronized(object) synchronized(this) 表示进入同步代码库前要获得 给定对象的锁
  • synchronized(类.class) 表示进入同步代码前要获得 给定 Class 的锁
synchronized(this) {
    //业务代码
}

4、类锁有两种实现方式:

  1. synchronized加在static方法上(静态方法锁)。
  2. synchronized(*.class)代码块。

面试题三、Lock锁与synchronized的区别

  1. synchronized是Java语法的一个关键字,加锁的过程是在JVM底层进行。
  2. Lock是一个类,是JDK应用层面的,在JUC包里有丰富的API。
  3. synchronized在加锁和解锁操作上都是自动完成的,Lock锁需要我们手动加锁和解锁。
  4. Lock锁有丰富的API能知道线程是否获取锁成功,而synchronized不能。
  5. synchronized能修饰方法和代码块,Lock锁只能锁住代码块。 Lock锁有丰富的API,可根据不同的场景,在使用上更加灵活。
  6. synchronized是非公平锁,而Lock锁既有非公平锁也有公平锁,可以由开发者通过参数控制。
  • 29
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值