面试题答案
一键面试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
修饰代码块时,字节码层面会使用monitorenter
和monitorexit
指令。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
进行安全的更新操作。