面试题答案
一键面试1. synchronized在Java内存模型中实现同步与互斥的原理
- 基本原理:synchronized关键字通过对象监视器(monitor)来实现同步与互斥。每个对象都关联着一个monitor,当线程获取到monitor,就相当于获取到了该对象的锁。在同一时刻,只有一个线程能够持有对象的monitor,其他线程若想获取该monitor,就必须等待。这就实现了对共享资源的互斥访问。
2. 在对象头中的标识
- 对象头结构:在HotSpot虚拟机中,对象头(Object Header)主要由两部分组成,一部分是Mark Word,另一部分是类型指针(指向对象的类元数据)。对于数组对象,还会有一部分用于记录数组长度。
- Mark Word:Mark Word用于存储对象的一些运行时数据,比如哈希码(HashCode)、对象分代年龄、锁状态标志等。不同的锁状态下,Mark Word的存储内容不同。
- 锁状态标识:
- 无锁状态:Mark Word存储对象的哈希码、对象分代年龄等信息。
- 偏向锁状态:Mark Word存储偏向线程ID、偏向时间戳等信息。
- 轻量级锁状态:Mark Word存储指向栈中锁记录(Lock Record)的指针。
- 重量级锁状态:Mark Word存储指向对象监视器(monitor)的指针。
3. 锁升级的过程
- 偏向锁:
- 适用场景:适用于只有一个线程频繁访问同步块的场景。
- 获取过程:当一个线程访问同步块并获取锁时,会在对象头的Mark Word中记录该线程的ID。之后该线程再次访问该同步块时,只需要检查Mark Word中的线程ID是否与自己的ID一致,如果一致则无需进行额外的同步操作,直接进入同步块。
- 撤销过程:当有其他线程尝试获取锁时,偏向锁会被撤销。偏向锁的撤销需要等待全局安全点(Safe Point),在安全点上,检查持有偏向锁的线程是否还存活。如果不存活,直接撤销偏向锁;如果存活,将偏向锁升级为轻量级锁。
- 轻量级锁:
- 适用场景:适用于多个线程交替访问同步块,但竞争不激烈的场景。
- 获取过程:当线程进入同步块时,会在自己的栈帧中创建一个锁记录(Lock Record),将对象头中的Mark Word复制到锁记录中,然后尝试使用CAS操作将对象头中的Mark Word替换为指向锁记录的指针。如果替换成功,该线程就获取到了轻量级锁。
- 膨胀过程:如果有多个线程同时竞争轻量级锁,CAS操作失败,轻量级锁就会膨胀为重量级锁。此时,对象头中的Mark Word会存储指向对象监视器(monitor)的指针。
- 重量级锁:
- 适用场景:适用于竞争激烈的场景。
- 获取过程:当线程获取重量级锁时,会将monitor的进入数加1。如果monitor的进入数为0,表示该锁可用,线程获取锁成功;否则,线程进入等待队列,等待被唤醒。
4. 对可见性的影响
- 内存屏障:synchronized关键字通过内存屏障来保证可见性。当一个线程释放锁(退出同步块)时,会将该线程本地内存中的共享变量刷新到主内存中;当另一个线程获取锁(进入同步块)时,会将主内存中的共享变量重新加载到自己的本地内存中。
- happens - before原则:根据Java内存模型的happens - before原则,对一个锁的解锁操作happens - before后续对同一个锁的加锁操作。这就保证了在释放锁之前对共享变量的修改对后续获取锁的线程是可见的。