synchronized 的底层原理(monitorenter 、monitorexit、ACC_SYNCHRONIZED)

目录

什么是 synchronized ?

如何使用synchronized

修饰实例方法

修饰静态方法

修饰代码块

synchronized 的底层原理

synchronized 在修饰同步代码块

synchronized 在修饰方法

总结


什么是 synchronized ?

synchronized 是 Java 中的一个关键字,主要解决的是多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

如何使用synchronized

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

  1. 修饰实例方法

  2. 修饰静态方法

  3. 修饰代码块

修饰实例方法

给当前对象实例加锁,进入代码块要获取对象实例的锁。

     public synchronized void method() {
        System.out.println("synchronized 方法");
    }

修饰静态方法

给当前类加锁,进入代码块要索取当前class的锁。

    public synchronized static void method() {
        System.out.println("synchronized 静态方法");
    }

静态 synchronized 方法和非静态 synchronized 方法之间的调用互斥么?

答案是不会的。因为他们使用的不是同一个锁,非静态 synchronized 方法使用的是当前实例对象锁,而静态 synchronized 方法使用的是当前类的锁。

修饰代码块

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

  • synchronized(object) 表示进入同步代码库前要获得 给定对象的锁
  • synchronized(类.class) 表示进入同步代码前要获得 给定 Class 的锁。
    public void method() {
        synchronized (this) {
            System.out.println("synchronized 代码块");
        }
    }

注意:构造方法不能使用 synchronized 关键字修饰。不过,可以在构造方法内部使用synchronized 代码块。

synchronized 的底层原理

synchronized 在修饰同步代码块和修饰方法上有所区别。

synchronized 在修饰同步代码块

使用javap 命令查看类的相关字节码信息。

synchronized 同步语句块的实现使用的是 monitorenter monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

使用两个 monitorexit 指令,这是为了保证锁在同步代码块代码正常执行以及出现异常的这两种情况下都能被正确释放。

当执行 monitorenter 指令时,线程试图获取锁也就是获取 对象监视器 monitor 的持有权。

执行 monitorenter 指令流程:

(1)如果monitor的计数器为0,则意味着该monitor的锁还没有被获得,某个线程获得之后将立即对该计数器加一,然后线程就是这个monitor的所有者了。

(2)如果一个已经拥有该monitor的所有权的线程重入,则会导致monitor计数器再次累加1。

(3)如果monitor已经被其他线程所拥有,则其他线程尝试获取该monitor的所有权时,会被陷入阻塞状态直到monitor计数器变为0,才能再次尝试获取对monitor的所有权。

执行 monitorexit 指令流程:

对象锁的的拥有者线程才可以执行 monitorexit 指令来释放锁。在执行 monitorexit 指令后,将锁计数器减1,直到计数器为0时,表明锁被释放,其他线程可以尝试获取锁。

synchronized 在修饰方法

使用javap 命令查看类的相关字节码信息。

synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,而是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

如果是实例方法,JVM 会尝试获取实例对象的锁。如果是静态方法,JVM 会尝试获取当前 class 的锁。

总结

  • synchronized 修饰同步代码块,底层是使用monitorenter monitorexit 指令实现的,monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。
  • synchronized 修饰方法,底层使用 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。

两种情况都需要依赖对象监视器 monitor:

  • Monitor 是jvm级别的对象,用c++语言实现。线程获得锁需要使用对象(锁)关联monitor。

  • 在monitor内部有三个属性,分别是owner、entrylistwaitset

  • 其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于Waiting状态的线程。

### ACC_SYNCHRONIZED 的定义与作用 ACC_SYNCHRONIZEDJava 字节码中的一个方法访问标志,其值为 `0x0020`[^3]。当一个方法被标记为 `ACC_SYNCHRONIZED` 时,表示该方法是同步的(synchronized)。这意味着在 JVM 执行此方法时,会自动获取与该方法所属对象相关的监视器(monitor lock),并在方法执行完成后释放。对于静态方法,JVM获取与类本身相关的。 同步方法的主要目的是确保在多线程环境中,同一时间只有一个线程能够执行该方法,从而避免数据竞争不一致的状态[^3]。 ### 技术实现细节 当一个方法被声明为 `synchronized` 时,编译器会在生成的 `.class` 文件中设置 `ACC_SYNCHRONIZED` 标志位。具体来说: - 对于实例方法,JVM 在调用该方法时会先定当前对象(`this`)。 - 对于静态方法,JVM定该类的 `Class` 对象。 以下是同步方法的一个简单示例及其字节码表现: ```java public class Example { public synchronized void syncMethod() { System.out.println("This is a synchronized method."); } } ``` 编译后,`syncMethod` 方法会被标记为 `ACC_SYNCHRONIZED`。对应的字节码可能如下所示: ```plaintext flags: ACC_PUBLIC, ACC_SYNCHRONIZED ``` ### 字节码指令的影响 在字节码层面,`ACC_SYNCHRONIZED` 标志的存在并不会直接转化为特定的字节码指令。相反,它会影响 JVM 的行为。当 JVM 遇到带有 `ACC_SYNCHRONIZED` 标志的方法时,会在方法调用前后自动插入操作[^4]。例如: - 调用同步方法时,JVM 会执行类似于 `monitorenter` 的操作来获取。 - 方法返回或抛出异常时,JVM 会执行类似于 `monitorexit` 的操作来释放。 需要注意的是,`monitorenter` `monitorexit` 指令通常不会显式出现在字节码中,而是由 JVM 自动处理。 ### 与其他标志的对比 ACC_SYNCHRONIZED 与其他方法访问标志(如 `ACC_PUBLIC`、`ACC_PRIVATE`、`ACC_STATIC` 等)类似,都是用来描述方法属性的标志位。不同之处在于,`ACC_SYNCHRONIZED` 主要用于控制线程安全,而其他标志则主要用于访问控制或方法类型定义。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值