找回密码
 立即注册
首页 业界区 业界 从MESA模型到锁升级:synchronized性能逆袭的底层逻辑 ...

从MESA模型到锁升级:synchronized性能逆袭的底层逻辑

龙玮奇 2025-9-25 09:23:32
管程(Monitor)是一种用于管理共享资源访问的程序结构,能确保同一时刻只有一个线程访问共享资源,解决并发编程中的互斥和同步问题。MESA模型是管程的经典实现,主要由入口等待队列和条件变量等待队列构成。
1)入口等待队列‌:确保线程互斥,多个线程试图进入管程时,仅一个线程能成功,其余线程在入口等待队列中排队。
2)条件变量等待队列‌:解决线程同步问题,线程在管程内执行时,若条件不满足需等待其他线程操作结果,则进入相应条件变量的等待队列。
当线程被notify或notifyAll唤醒后,不会立即执行,而是先进入入口等待队列竞争管程的锁。只有竞争到锁后,线程才能继续执行。因此,被唤醒的线程需循环检验条件是否满足,即采用while (条件不满足) { wait(); } 的编程范式,以避免条件不一致问题。
1.png

synchronized参考了MESA管程模型,对MESA模型进行了精简。在MESA 模型中,一个管程可以有多个条件变量,而Java中的synchronized机制只对应一个条件变量。
  1. public class Test {
  2.     public static void main(String[] args) {
  3.         // 同步代码块方式加锁
  4.         synchronized (Test.class) {
  5.         }
  6.         // 同步方法方式加锁
  7.         func();
  8.     }
  9.    
  10.     public static synchronized void func() {
  11.     }
  12. }
复制代码
先使用javac编译,生成Test.class的文件。使用javap -c命令来查看字节码。
  1. public static void main(java.lang.String[]);
  2.     descriptor: ([Ljava/lang/String;)V
  3.     flags: ACC_PUBLIC, ACC_STATIC
  4.     Code:
  5.       stack=2, locals=3, args_size=1
  6.          0: ldc           #2                  // class com/tencent/trpcprotocol/dayu/identify/Test
  7.          2: dup
  8.          4: monitorenter
  9.          6: monitorexit
  10.         12: monitorexit
  11.         15: invokestatic  #3                  // Method func:()V
  12.         18: return
  13.   public static synchronized void func();
  14.     descriptor: ()V
  15.     flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
  16.     Code:
  17.       stack=0, locals=0, args_size=0
  18.          0: return
  19.       LineNumberTable:
  20.         line 25: 0
复制代码
从字节码可以看出,同步代码块通过monitorenter和monitorexit指令实现锁的获取与释放,而同步方法通过ACC_SYNCHRONIZED标记隐式管理锁。无论采用哪种方式,其本质是对一个对象(Object)的监视器锁(Monitor locking)进行获取,它与synchronized 所在的对象一一对应。
当一个线程进入一个synchronized方法或代码块时,它会尝试获取该对象的监视器锁。如果锁没有被其他线程占用,该线程会获取到锁,并执行临界区的代码。如果锁已经被其他线程占用,该线程会进入阻塞(BLOCKED)状态,并进入同步队列等待锁的释放。
Java 中的 Object 类提供了wait()、notify() 和 notifyAll()方法,这些方法正是基于MESA 模型中的条件变量实现的。当线程执行wait()方法时,会释放锁,并将线程从运行状态转移到等待队列中;当线程被notify或notifyAll 唤醒后,会重新进入同步队列,参与锁的竞争,竞争成功后才能继续执行。
2.png

synchronized性能优化
synchronized在早期仅支持‌重量级锁‌(Mutex locking),依赖操作系统内核态与用户态的切换,性能较差。JDK 6后引入多级锁优化。
1)偏向锁(Biased Locking):针对同一线程反复获取同一锁的场景,偏向锁会记录首次获取锁的线程ID。后续该线程再次获取锁时,无需同步操作即可直接执行,从而消除不必要的锁竞争开销。
2)轻量级锁(Lightweight Locking):通过‌CAS操作和锁标记位实现。线程尝试以CAS方式将锁标记为轻量级状态,若成功则直接获取锁;若失败,则通过自旋等待锁释放。此机制在竞争不激烈时避免了内核态切换,显著提升锁操作效率。
3)自旋锁(Spin Locking):当锁获取失败时,线程会在有限次数内循环等待(自旋),而非立即进入阻塞状态。适用于锁持有时间极短的场景,通过减少线程挂起与唤醒开销提升性能。
4)锁消除(Lock Elimination):Java虚拟机的即时编译器在运行时分析代码,若检测到某些锁操作(如对局部对象的加锁)无实际意义,会自动移除这些锁,从而优化程序性能。
synchronized使用注意
1)避免死锁‌:如果两个或多个线程互相等待对方释放锁,会导致死锁。由于synchronized 不提供超时机制,可以使用 JUC并发包的ReentrantLock 并设置超时时间来避免死锁。
  1. public class DeadlockAvoidance {
  2.     private final Object lock1 = new Object();
  3.     private final Object lock2 = new Object();
  4.     // 线程1
  5.     public void method1() {
  6.         synchronized (lock1) {
  7.             System.out.println(Thread.currentThread().getName() + " locked lock1");
  8.             synchronized (lock2) {
  9.                 System.out.println(Thread.currentThread().getName() + " locked lock2");
  10.             }
  11.         }
  12.     }
  13.     // 线程2
  14.     public void method2() {
  15.         synchronized (lock1) {
  16.             System.out.println(Thread.currentThread().getName() + " locked lock1");
  17.             synchronized (lock2) {
  18.                 System.out.println(Thread.currentThread().getName() + " locked lock2");
  19.             }
  20.         }
  21.     }
  22. }
复制代码
‌2)锁粒度:锁粒度指的是对共享资源加锁的范围。锁的粒度过大,会导致并发性能下降;锁的粒度过小,会增加锁管理的开销。
  1. public class SynchronizedBlockExample {
  2.     private int count = 0;
  3.     // 只同步增量操作部分,避免同步无关代码
  4.     public void increment() {
  5.         synchronized (this) {
  6.             count++;
  7.         }
  8.     }
  9.     public int getCount() {
  10.         return count;
  11.     }
  12. }
复制代码
3)理解可重入性:synchronized 是可重入的,也就是说,同一个线程可以多次获得同一个锁而不会发生死锁
  1. public class ReentrantLockExample {
  2.     // 如果一个线程在同步方法内部调用了另一个同步方法,它仍然能获取锁
  3.     public synchronized void methodA() {
  4.         System.out.println("Entering method A");
  5.         methodB();  // 调用另一个同步方法
  6.     }
  7.     public synchronized void methodB() {
  8.         System.out.println("Entering method B");
  9.     }
  10. }
复制代码
未完待续
很高兴与你相遇!如果你喜欢本文内容,记得关注哦

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册