Java中有关锁的面试题(部分)

Java 中关于锁的面试题经常涉及 锁的类型、锁的机制、锁的优化以及锁的底层原理。以下是一些高频面试题,并附带详细解答,希望能帮助你准备面试!


1. Java 有哪些锁?它们的区别是什么?

(1)按锁的范围分类

  • 对象锁(作用于实例对象,影响同一个实例的多个线程)
  • 类锁(作用于 Class 对象,影响同一个类的所有实例)

(2)按锁的实现方式分类

  • 偏向锁(Biased Locking):只有一个线程访问时,会偏向该线程,减少锁的开销(无竞争时性能最佳)。
  • 轻量级锁(Lightweight Locking):多个线程竞争时,尝试使用 CAS(无锁操作)来争夺锁(适用于低竞争场景)。
  • 重量级锁(Heavyweight Locking):多个线程竞争失败后,锁升级为重量级锁,使用 操作系统级的互斥锁,导致线程阻塞(适用于高竞争场景)。

(3)按锁的可重入性分类

  • 可重入锁(Reentrant Lock):同一线程可以多次获取同一把锁,例如 synchronizedReentrantLock
  • 不可重入锁:同一线程无法重复获取锁,否则会导致死锁。

(4)按公平性分类

  • 公平锁:按照请求顺序获取锁,如 ReentrantLock(true)
  • 非公平锁:线程可能会插队获取锁,提高吞吐量,如 ReentrantLock(false)默认非公平锁)。

(5)按共享和独占分类

  • 独占锁(Exclusive Lock):同一时间只有一个线程能持有锁,如 ReentrantLock
  • 共享锁(Shared Lock):多个线程可以同时读数据,但不能同时写,如 ReadWriteLock

2. synchronizedReentrantLock 的区别?

对比项 synchronized ReentrantLock
锁的类型 由 JVM 实现 由 JDK 提供
可重入性 (一个线程可以多次获取同一锁)
公平性 非公平锁 支持公平和非公平(默认非公平)
读写分离 不支持 ReadWriteLock 支持读写分离
阻塞与非阻塞 阻塞式 可选非阻塞 tryLock()
可中断性 不支持 lockInterruptibly() 支持可中断
超时机制 不支持 tryLock(timeout, TimeUnit.SECONDS)

总结

  • synchronized 适合简单的同步场景,性能更好(JVM 进行了大量优化)
  • ReentrantLock 适合复杂的并发控制,如公平锁、可中断锁、超时锁等。

3. synchronized 底层是如何实现的?

synchronized 依赖于 JVM 对象头的 Mark Word,通过 锁升级机制(偏向锁 → 轻量级锁 → 重量级锁) 实现高效同步。其底层实现涉及:

  1. 偏向锁:无竞争时,使用 CAS + Mark Word 记录线程 ID,避免频繁使用互斥量。
  2. 轻量级锁:竞争时,多个线程通过 CAS 方式加锁,失败则进入自旋。
  3. 重量级锁:竞争严重时,升级为重量级锁(Monitor),线程阻塞,等待唤醒。

synchronized 主要依赖 JVM 内部的 monitorentermonitorexit 指令,它会使用 对象头的 Mark Word 记录锁状态

示例

public class SyncDemo {
    private final Object lock = new Object();

    public void method() {
        synchronized (lock) {
            System.out.println("线程安全的方法");
        }
    }
}
JVM 反编译字节码
javap -v SyncDemo.class

会看到:

monitorenter  // 进入 synchronized 块
monitorexit   // 退出 synchronized 块

4. 什么是 CAS(Compare-And-Swap),有什么优缺点?

(1)CAS 工作原理

CAS 是无锁并发的一种实现方式,用于原子操作,如 AtomicInteger。它的核心是 比较并交换

  • 读取旧值(expectedValue)。
  • 如果旧值未被其他线程修改,则将其更新为新值。
  • 否则,更新失败,需要重试。

示例:

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) {
        count.compareAndSet(0, 10);  // 只有当 count 为 0 时,才更新为 10
        System.out.println(count.get());  // 输出 10
    }
}

(2)CAS 的优缺点

✅ 优点

  • 无锁操作,减少了上下文切换,提升并发性能。
  • 适用于高并发环境,避免了传统的 synchronized 竞争。

❌ 缺点

  1. ABA 问题(解决方案:使用 AtomicStampedReference 加时间戳)。
  2. 自旋消耗 CPU(如果失败后一直重试,会占用大量 CPU 资源)。
  3. 只能保证一个变量的原子性(如果多个变量需要同时更新,需要使用 AtomicReferencesynchronized)。

5. 什么是死锁?如何避免?

(1)死锁的产生条件

死锁发生时,多个线程相互等待,导致程序无法继续执行。死锁的四个必要条件:

  1. 互斥条件(只能一个线程占有资源)。
  2. 请求并持有(线程已持有资源,还在等待新的资源)。
  3. 不可剥夺(资源不能被强行抢占)。
  4. 循环等待(多个线程形成循环等待链)。

(2)死锁示例

class DeadLockDemo {
    private static final Object LOCK1 = new Object();
    private static final Object LOCK2 = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (LOCK1) {
                System.out.println("线程1 持有 LOCK1");
                synchronized (LOCK2) {
                    System.out.println("线程1 获取 LOCK2");
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (LOCK2) {
                System.out.println("线程2 持有 LOCK2");
                synchronized (LOCK1) {
                    System.out.println("线程2 获取 LOCK1");
                }
            }
        }).start();
    }
}

(3)如何避免死锁?

  1. 避免嵌套锁(尽量减少持有锁的时间)。
  2. 使用 tryLock()(避免无限等待)。
  3. 为多个资源加锁时,始终按固定顺序加锁(避免循环等待)。
  4. 使用 Thread.dumpStack() 检测死锁

6. 什么是自旋锁?

自旋锁 是指线程在获取锁时不会立即阻塞,而是 不断尝试获取锁(自旋),直到成功
JVM 默认自旋 10 次(可通过 -XX:PreBlockSpin 调整)。

优点:减少线程切换,提高性能。
缺点:若竞争激烈,会浪费 CPU 资源。


这些是 Java 并发锁的常见面试题,涉及锁的类型、底层原理、CAS、死锁以及锁优化,熟练掌握后能应对大多数面试!

你可能感兴趣的:(java相关锁,java,开发语言,面试)