面试题答案
一键面试偏向锁
- 工作原理:
- 偏向锁是为了在无竞争的情况下尽量减少锁获取和释放的开销。当一个线程首次访问同步块并获取锁时,会在对象头中记录这个线程的ID,表示该线程偏向于这个锁。后续该线程再次进入这个同步块时,只需检查对象头中的偏向线程ID是否与自己一致,如果一致则无需再进行CAS操作获取锁,直接进入同步块,大大节省了锁获取的开销。
- 当有其他线程尝试竞争偏向锁时,偏向锁会被撤销,升级为轻量级锁。
- 对Java内存模型的影响:
- 偏向锁的存在不涉及复杂的同步操作,对Java内存模型的影响较小。它主要是通过对象头记录偏向线程ID来实现高效的锁获取,在偏向状态下,线程对同步块的访问不会触发Java内存模型中的同步屏障等复杂操作,从而减少了内存可见性相关的开销。
- 适用场景:
- 适用于锁基本总是由同一线程访问的场景。例如,单线程执行的代码块,或者大部分时间只有一个线程会访问同步块的场景,如一些初始化操作只在程序启动阶段由一个线程执行,后续不再有竞争。
轻量级锁
- 工作原理:
- 当偏向锁升级为轻量级锁时,线程会在自己的栈帧中创建一个锁记录(Lock Record),并将对象头中的Mark Word复制到锁记录中。然后线程尝试使用CAS操作将对象头中的Mark Word替换为指向自己锁记录的指针。如果CAS操作成功,该线程就获取到了轻量级锁,进入同步块。
- 如果CAS操作失败,说明有其他线程竞争锁。此时,轻量级锁会膨胀为重量级锁,通过操作系统的互斥量来实现线程同步。
- 对Java内存模型的影响:
- 轻量级锁的获取和释放主要依赖CAS操作,CAS操作是一种原子操作,它确保了在多线程环境下对共享变量的原子更新。这会涉及到Java内存模型中的一些规则,如volatile变量的可见性保证等。因为轻量级锁的竞争可能会导致锁膨胀,而重量级锁的实现依赖操作系统的互斥量,会涉及到线程的阻塞和唤醒,这会对Java内存模型中的线程同步机制产生影响,如线程状态的切换可能会影响内存可见性。
- 适用场景:
- 适用于短时间内有竞争,但竞争不激烈的场景。例如,在一些并发访问但访问时间较短的同步块中,轻量级锁可以避免重量级锁带来的线程阻塞和唤醒的开销,提高程序性能。
自旋锁
- 工作原理:
- 自旋锁是在轻量级锁竞争失败时,线程不会立即阻塞,而是在一段时间内进行自旋,即不断尝试获取锁。自旋的目的是期望在短时间内持有锁的线程能够释放锁,这样自旋的线程就可以避免线程上下文切换的开销,直接获取到锁。
- 自旋的次数通常是有限的,当自旋次数达到一定阈值后,如果仍然没有获取到锁,线程就会进入阻塞状态,将CPU资源让给其他线程。
- 对Java内存模型的影响:
- 自旋锁在自旋过程中,线程一直占用CPU资源,不断尝试获取锁,这会影响Java内存模型中线程调度的策略。同时,由于自旋过程中线程不会释放CPU,可能会导致其他线程无法及时获取CPU资源,影响整体的系统性能。但如果自旋能够在短时间内获取到锁,就可以避免线程上下文切换带来的内存可见性等相关开销。
- 适用场景:
- 适用于锁持有时间短、竞争不激烈的场景。例如,在一些高频但短时间的同步操作中,自旋锁可以有效避免线程上下文切换的开销,提高多线程程序的性能。但如果锁持有时间长,自旋会浪费CPU资源,反而降低性能,此时应避免使用自旋锁。
锁优化策略选择
- 无竞争场景:优先使用偏向锁,因为偏向锁在无竞争情况下锁获取开销最小,能极大提升单线程执行同步块的效率。
- 竞争不激烈且同步块执行时间短的场景:可选择轻量级锁或自旋锁。如果竞争的线程数量较少,轻量级锁的CAS操作能高效获取锁;如果竞争的线程预计持有锁时间较短,自旋锁能避免线程上下文切换的开销,提高性能。
- 竞争激烈或锁持有时间长的场景:应考虑使用重量级锁,虽然重量级锁的线程阻塞和唤醒开销大,但在这种场景下,它能保证线程安全和系统资源的合理分配,避免自旋或轻量级锁带来的CPU资源浪费等问题。