Skip to content

锁机制面试题

1. 锁的基本概念

问题:什么是锁?为什么要使用锁?

答案

  • :用于控制多个线程访问共享资源的机制。
  • 作用
    • 保证线程安全。
    • 避免数据竞争。
    • 保证数据一致性。

2. synchronized关键字

问题:synchronized关键字的作用是什么?

答案

  • 作用
    • 保证原子性。
    • 保证可见性。
    • 保证可重入性。
  • 使用方式
    • 修饰实例方法。
    • 修饰静态方法。
    • 修饰代码块。

示例

java
// 修饰实例方法
public synchronized void method() {
}

// 修饰静态方法
public static synchronized void staticMethod() {
}

// 修饰代码块
public void method() {
    synchronized (this) {
    }
}

3. synchronized的实现原理

问题:synchronized的实现原理是什么?

答案

  • 对象头:synchronized使用对象头中的Mark Word实现。
  • Monitor:Monitor是同步的基础,每个对象都有一个Monitor。
  • 锁状态:无锁、偏向锁、轻量级锁、重量级锁。
  • 锁升级:偏向锁 -> 轻量级锁 -> 重量级锁。

4. ReentrantLock

问题:ReentrantLock的特点是什么?

答案

  • 特点
    • 可重入锁。
    • 可中断锁。
    • 可设置超时。
    • 支持公平锁。
    • 支持多个条件。

示例

java
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
} finally {
    lock.unlock();
}

5. ReentrantLock和synchronized的区别

问题:ReentrantLock和synchronized有什么区别?

答案

  • 实现方式:ReentrantLock基于AQS,synchronized基于Monitor。
  • 可中断:ReentrantLock可中断,synchronized不可中断。
  • 超时:ReentrantLock可设置超时,synchronized不可设置超时。
  • 公平锁:ReentrantLock支持公平锁,synchronized不支持。
  • 条件:ReentrantLock支持多个条件,synchronized只支持一个。
  • 释放锁:ReentrantLock需要手动释放,synchronized自动释放。

6. ReentrantReadWriteLock

问题:ReentrantReadWriteLock的特点是什么?

答案

  • 特点
    • 支持读锁和写锁。
    • 读锁共享,写锁独占。
    • 支持锁降级。
    • 不支持锁升级。

示例

java
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

// 读锁
readLock.lock();
try {
} finally {
    readLock.unlock();
}

// 写锁
writeLock.lock();
try {
} finally {
    writeLock.unlock();
}

7. StampedLock

问题:StampedLock的特点是什么?

答案

  • 特点
    • 支持乐观读。
    • 支持悲观读。
    • 支持写锁。
    • 性能优于ReentrantReadWriteLock。

示例

java
StampedLock lock = new StampedLock();

// 乐观读
long stamp = lock.tryOptimisticRead();
if (!lock.validate(stamp)) {
    stamp = lock.readLock();
    try {
    } finally {
        lock.unlockRead(stamp);
    }
}

// 悲观读
long stamp = lock.readLock();
try {
} finally {
    lock.unlockRead(stamp);
}

// 写锁
long stamp = lock.writeLock();
try {
} finally {
    lock.unlockWrite(stamp);
}

8. LockSupport

问题:LockSupport的作用是什么?

答案

  • 作用
    • 阻塞线程。
    • 唤醒线程。
    • 基于Unsafe实现。

示例

java
// 阻塞线程
LockSupport.park();

// 唤醒线程
LockSupport.unpark(thread);

9. Condition

问题:Condition的作用是什么?

答案

  • 作用
    • 替代wait/notify。
    • 支持多个条件。
    • 更灵活的线程通信。

示例

java
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

// 等待
lock.lock();
try {
    condition.await();
} finally {
    lock.unlock();
}

// 唤醒
lock.lock();
try {
    condition.signal();
} finally {
    lock.unlock();
}

10. AQS

问题:AQS的实现原理是什么?

答案

  • AQS(AbstractQueuedSynchronizer):抽象队列同步器。
  • 原理
    • 使用volatile int state表示同步状态。
    • 使用CLH队列管理等待线程。
    • 使用CAS操作保证原子性。

11. 自旋锁

问题:什么是自旋锁?

答案

  • 自旋锁:线程在获取锁时不会阻塞,而是循环尝试获取锁。
  • 优点:减少线程上下文切换。
  • 缺点:占用CPU资源。

示例

java
public class SpinLock {
    private AtomicBoolean locked = new AtomicBoolean(false);
    
    public void lock() {
        while (!locked.compareAndSet(false, true)) {
        }
    }
    
    public void unlock() {
        locked.set(false);
    }
}

12. 公平锁和非公平锁

问题:公平锁和非公平锁有什么区别?

答案

  • 公平锁:按照请求锁的顺序获取锁。
  • 非公平锁:不按照请求锁的顺序获取锁,可能插队。
  • 性能:非公平锁性能更好。

示例

java
// 公平锁
ReentrantLock lock = new ReentrantLock(true);

// 非公平锁
ReentrantLock lock = new ReentrantLock(false);

13. 可重入锁

问题:什么是可重入锁?

答案

  • 可重入锁:同一个线程可以多次获取同一个锁。
  • 特点
    • 避免死锁。
    • 支持递归调用。

示例

java
public class ReentrantExample {
    public synchronized void method1() {
        method2();
    }
    
    public synchronized void method2() {
    }
}

14. 锁的优化

问题:如何优化锁?

答案

  • 减小锁的粒度:使用ConcurrentHashMap。
  • 减小锁的持有时间:只锁必要的代码。
  • 使用读写锁:读多写少场景使用ReentrantReadWriteLock。
  • 使用乐观锁:使用CAS操作。
  • 使用分段锁:使用ConcurrentHashMap的Segment。
  • 使用锁消除:JVM自动消除不必要的锁。

15. 锁的升级

问题:synchronized的锁升级过程是什么?

答案

  • 无锁:没有线程竞争。
  • 偏向锁:只有一个线程访问,使用CAS将线程ID设置到对象头。
  • 轻量级锁:有多个线程竞争,使用CAS将Mark Word替换为锁记录。
  • 重量级锁:竞争激烈,使用Monitor。

16. 锁的降级

问题:什么是锁降级?

答案

  • 锁降级:将写锁降级为读锁。
  • 场景:ReentrantReadWriteLock支持锁降级。

示例

java
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();

writeLock.lock();
try {
    readLock.lock();
} finally {
    writeLock.unlock();
}

try {
} finally {
    readLock.unlock();
}

17. 死锁

问题:什么是死锁?如何避免?

答案

  • 死锁:两个或多个线程互相等待对方释放锁。
  • 避免方法
    • 按照固定顺序获取锁。
    • 使用tryLock设置超时。
    • 使用死锁检测。
    • 减少锁的持有时间。

示例

java
// 按照固定顺序获取锁
synchronized (lock1) {
    synchronized (lock2) {
    }
}

// 使用tryLock
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
    try {
        if (lock2.tryLock(1, TimeUnit.SECONDS)) {
            try {
            } finally {
                lock2.unlock();
            }
        }
    } finally {
        lock1.unlock();
    }
}

18. 活锁

问题:什么是活锁?如何避免?

答案

  • 活锁:线程不断改变状态,但无法继续执行。
  • 避免方法
    • 增加随机等待时间。
    • 设置重试次数限制。

19. 锁的内存语义

问题:锁的内存语义是什么?

答案

  • 内存语义
    • 获取锁:刷新工作内存,从主内存读取数据。
    • 释放锁:刷新工作内存,将数据写入主内存。
    • happens-before:解锁操作 happens-before 加锁操作。

20. 锁的可见性

问题:锁如何保证可见性?

答案

  • 可见性
    • 获取锁时,从主内存读取数据。
    • 释放锁时,将数据写入主内存。
    • 使用volatile保证可见性。

21. 锁的原子性

问题:锁如何保证原子性?

答案

  • 原子性
    • synchronized使用Monitor保证原子性。
    • ReentrantLock使用AQS保证原子性。
    • 使用CAS操作保证原子性。

22. 锁的有序性

问题:锁如何保证有序性?

答案

  • 有序性
    • synchronized保证同一时间只有一个线程执行。
    • ReentrantLock保证同一时间只有一个线程执行。
    • 使用volatile保证有序性。

23. 锁的监控

问题:如何监控锁?

答案

java
ReentrantLock lock = new ReentrantLock();

// 获取锁的状态
boolean isLocked = lock.isLocked();
boolean isHeldByCurrentThread = lock.isHeldByCurrentThread();
int holdCount = lock.getHoldCount();
int queueLength = lock.getQueueLength();
boolean hasQueuedThreads = lock.hasQueuedThreads();
boolean hasQueuedThread = lock.hasQueuedThread(thread);

// 获取公平锁的状态
boolean isFair = lock.isFair();

24. 锁的中断

问题:如何中断锁?

答案

java
ReentrantLock lock = new ReentrantLock();

// 可中断的锁
lock.lockInterruptibly();

// 带超时的锁
lock.tryLock(1, TimeUnit.SECONDS);

25. 锁的最佳实践

问题:使用锁的最佳实践有哪些?

答案

  • 减小锁的粒度。
  • 减小锁的持有时间。
  • 使用tryLock避免死锁。
  • 使用读写锁提高并发性能。
  • 使用乐观锁减少锁竞争。
  • 使用StampedLock提高性能。
  • 及时释放锁。
  • 避免在锁中执行耗时操作。
  • 使用LockSupport阻塞和唤醒线程。
  • 使用Condition实现线程间通信。