MST

星途 面试题库

面试题:Java中synchronized关键字实现锁机制的原理是什么

请详细阐述Java中synchronized关键字是如何实现对象锁和类锁机制的,从字节码层面以及JVM内部实现角度进行说明,并举例说明在不同场景下(如方法锁、代码块锁)的使用方式。
18.2万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

1. synchronized关键字实现对象锁和类锁机制

字节码层面

  • 对象锁:当使用synchronized修饰实例方法时,字节码层面会在方法的访问标志中增加ACC_SYNCHRONIZED标志。当线程调用该方法时,JVM会检查该方法是否有ACC_SYNCHRONIZED标志,如果有,线程会自动获取对象的监视器(monitor),进入方法时获取锁,退出方法时释放锁。例如:
public class ObjectLockExample {
    public synchronized void instanceMethod() {
        // 业务逻辑
    }
}

对应的字节码(简化示意):

  public synchronized void instanceMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
  • 类锁:当使用synchronized修饰静态方法时,字节码层面同样会在方法的访问标志中增加ACC_SYNCHRONIZED标志。由于静态方法属于类,所以此时获取的是类对应的Class对象的监视器,实现类锁机制。例如:
public class ClassLockExample {
    public static synchronized void staticMethod() {
        // 业务逻辑
    }
}

对应的字节码(简化示意):

  public static synchronized void staticMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=0, args_size=0
         0: return

synchronized修饰代码块时,字节码层面会使用monitorentermonitorexit指令。monitorenter指令会尝试获取对象的监视器,获取成功则持有锁,monitorexit指令用于释放锁。例如:

public class BlockLockExample {
    private final Object lock = new Object();
    public void blockSynchronizedMethod() {
        synchronized (lock) {
            // 业务逻辑
        }
    }
}

对应的字节码(简化示意):

  public void blockSynchronizedMethod();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: getfield      #2                  // Field lock:Ljava/lang/Object;
         4: dup
         5: astore_1
         6: monitorenter
         7: aload_1
         8: monitorexit
         9: goto          17
        12: astore_2
        13: aload_1
        14: monitorexit
        15: aload_2
        16: athrow
        17: return

JVM内部实现角度

  • 对象锁:在HotSpot虚拟机中,对象头(Object Header)包含了对象的一些元数据信息,其中有一部分用于存储对象的锁状态。当对象被作为锁时,线程通过CAS(Compare - And - Swap)操作尝试将对象头中的锁标志位设置为已锁定状态,如果设置成功则获取到锁。锁存在不同的状态,如偏向锁、轻量级锁和重量级锁,会根据竞争情况进行升级。
  • 类锁:类锁实际上就是类的Class对象的锁。每个类在JVM中都有唯一的Class对象,类锁的获取和释放也是基于这个Class对象的监视器。线程获取类锁时,同样是通过CAS操作尝试获取Class对象的监视器,实现对类相关资源的同步访问。

2. 不同场景下的使用方式

方法锁

  • 实例方法锁
public class MethodLockExample {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }
    public int getCount() {
        return count;
    }
}

在这个例子中,increment方法被synchronized修饰,成为实例方法锁。多个线程调用increment方法时,只有一个线程能获取到该实例对象的锁并执行方法,保证count变量的线程安全。

  • 静态方法锁
public class StaticMethodLockExample {
    private static int staticCount = 0;
    public static synchronized void staticIncrement() {
        staticCount++;
    }
    public static int getStaticCount() {
        return staticCount;
    }
}

这里staticIncrement方法是静态方法锁,多个线程调用staticIncrement方法时,获取的是类的Class对象的锁,从而实现对静态变量staticCount的线程安全操作。

代码块锁

  • 对象锁代码块
public class BlockLockWithObjectExample {
    private final Object lock = new Object();
    private int value = 0;
    public void updateValue() {
        synchronized (lock) {
            value++;
        }
    }
    public int getValue() {
        return value;
    }
}

updateValue方法中,使用synchronized修饰代码块,并以lock对象作为锁。这样多个线程调用updateValue方法时,只有获取到lock对象锁的线程才能进入代码块执行value++操作,保证value变量的线程安全。

  • 类锁代码块
public class BlockLockWithClassExample {
    private static int staticValue = 0;
    public void updateStaticValue() {
        synchronized (BlockLockWithClassExample.class) {
            staticValue++;
        }
    }
    public static int getStaticValue() {
        return staticValue;
    }
}

这里在updateStaticValue方法中,使用类的Class对象作为锁的代码块。多个线程调用updateStaticValue方法时,只有获取到BlockLockWithClassExample.class对象锁的线程才能进入代码块,对静态变量staticValue进行安全的更新操作。