Appearance
锁机制面试题
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实现线程间通信。
