Java并发-synchronized实现原理

Synchronized的实现原理

synchronized实现原理依赖于JVM的Monitor(监视器锁)和对象头(Object Header)。

当synchronized修饰在方法或代码块上时,会对特定的对象或类加锁,从而确保同一时刻只有一个线程能执行加索的代码块。

  • synchronized修饰方法:方法的常量池会增加一个ACC_SYNCHRONIZED标志,当某个线程访问这个方法检查是否有ACC_SYNCHRONIZED标志,若有则需要获得监视器锁才可以执行方法,保证方法的同步。

  • synchronized修饰代码块:会在代码块的前后插入monitorentermonitorexit字节码指令。可以把monitorenter理解为加锁,monitorexit理解为解锁。

synchronized关键字可以修饰代码块、实例方法和静态方法,本质上都是作用于对象。

对象头(Object Header)

在JVM中,每个对象的内存布局主要有两部分组成:

  • Mark Word:用于存储对象的运行时数据,包括锁状态、哈希码、GC分代信息等。

  • KIass Pointer:指向对象的类型元数据,帮助JVM确定对象的类型信息。

Mark Word是实现synchronized的关键,因为他会根据锁的状态保存不同的信息,具体包括:

  1. 未锁定状态
  2. 偏向锁状态
  3. 轻量级锁状态
  4. 重量级锁状态

锁的升级与优化

为了提高synchronized的性能,JVM从JDK1.6开始引入了锁的优化机制,包括偏向锁轻量级锁重量级锁,这些状态会根据线程的竞争情况就行动态升级降级

偏向锁

在没有锁竞争的情况下,锁总是“偏向”与第一个获得它的线程。偏向锁通过减少不必要的CAS操作来提高性能。

  • 加锁过程:当线程第一次请求锁时,JVM会将该线程的ID记录在对象头的Mark Word中,表示锁偏向于该线程。后续该线程再进入该锁时,无需进行额外的同步操作。

  • 撤销偏向锁:如果在偏向锁持有期间,另一个线程请求同一把锁,JVM会撤销偏向锁,并升级为轻量级锁。

轻量级锁

轻量级锁适用于多个线程短时间内争用同一锁的场景。

  • 加锁过程:当线程进入同步块时,JVM会在当前线程的栈帧中创建一个锁记录,并将对象头中的Mark Word拷贝到锁记录中。线程尝试使用CAS操作将对象头中的Mark Word更新为指向锁记录的指针。如果成功,则表示该线程获取了锁;如果失败,则表示其他线程已经持有该锁,此时锁会升级为重量级锁。

  • 解锁过程:线程退出同步块时,JVM会将对象头中的Mark Word恢复为原始值。

重量级锁

当锁竞争激烈时,JVM会升级为重量级锁,重量级锁使用操作系统的互斥量(Mutex)机制来实现线程的阻塞于唤醒。

  • 加锁过程:如果线程无法通过轻量级锁获取锁,JVM会将该锁升级为重量级锁,并将当前线程阻塞。
  • 当线程释放重量级锁时,JVM会唤醒所有阻塞的线程,允许它们再次尝试获取锁。

锁升级总结

  1. 偏向锁:当一个线程第一次获取锁时,JVM会将该线程标记为“偏向”状态,后续若该线程再次获取该锁,几乎没有开销。
  2. 轻量级锁:当另一个线程尝试获取已经被偏向的锁时,锁会升级为轻量级锁,使用CAS 操作来减少锁竞争的开销。
  3. 重量级锁:当CAS失败无法获取锁,锁会升级为重量级锁,线程会被挂起,直到锁被释放。

Synchronized的可重入性

synchronized是可重入的,每获取一次锁,计数器+1,释放锁时,计数器-1,直到为0,释放锁。