每日Java面试场景题知识点之-JUC锁的底层原理
引言
在Java并发编程中,锁机制是实现线程安全的核心。JUC(java.util.concurrent)包提供了丰富的锁工具类,理解这些锁的底层原理对于编写高性能的并发程序至关重要。本文将深入解析JUC中各种锁的底层实现原理。
1. ReentrantLock的可重入性原理
1.1 基本概念
ReentrantLock是JUC包中最常用的锁之一,它实现了Lock接口,提供了与synchronized类似的互斥性,但功能更强大。
java
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void method1() {
lock.lock();
try {
method2(); // 可重入调用
} finally {
lock.unlock();
}
}
public void method2() {
lock.lock();
try {
// 执行业务逻辑
} finally {
lock.unlock();
}
}
}
1.2 底层实现机制
ReentrantLock的底层基于AQS(AbstractQueuedSynchronizer)实现。AQS是一个构建锁和同步器的框架,它维护了一个volatile int state状态变量和一个CLH虚拟双向队列。
1.2.1 锁获取流程
- CAS尝试获取锁:首先通过CAS操作尝试将state从0改为1
- 成功获取:如果CAS成功,当前线程获得锁
- 失败入队:如果CAS失败,将线程封装为Node加入等待队列
- 自旋等待:在队列中自旋等待锁释放
1.2.2 可重入性实现
ReentrantLock通过记录持有锁的线程和重入次数来实现可重入性:
java
// AQS中的关键代码
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // 处理溢出
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
2. 读写锁(ReadWriteLock)原理
2.1 读写锁的概念
读写锁允许多个读线程同时访问共享资源,但写线程是独占的。这种锁适用于读多写少的场景。
java
public class ReadWriteLockExample {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
public void read() {
rwLock.readLock().lock();
try {
// 读操作
} finally {
rwLock.readLock().unlock();
}
}
public void write() {
rwLock.writeLock().lock();
try {
// 写操作
} finally {
rwLock.writeLock().unlock();
}
}
}
2.2 底层实现机制
读写锁内部维护了两个锁:读锁和写锁,它们共享同一个AQS状态变量。
2.2.1 状态变量设计
java
// 高16位为读锁计数,低16位为写锁计数
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
2.2.2 锁获取策略
- 读锁:当没有写锁时,可以获取多个读锁
- 写锁:当没有任何锁时,可以获取写锁
- 锁降级:写锁可以降级为读锁,但读锁不能升级为写锁
3. StampedLock原理
3.1 乐观读机制
StampedLock是Java 8引入的新锁,它提供了三种模式的锁:写锁、悲观读锁和乐观读锁。
java
public class StampedLockExample {
private final StampedLock stampedLock = new StampedLock();
private int data;
// 乐观读
public int optimisticRead() {
long stamp = stampedLock.tryOptimisticRead();
int currentData = data;
if (!stampedLock.validate(stamp)) {
// 如果数据被修改,重新获取悲观读锁
stamp = stampedLock.readLock();
try {
currentData = data;
} finally {
stampedLock.unlockRead(stamp);
}
}
return currentData;
}
}
3.2 底层实现特点
- 无锁读:乐观读不阻塞其他线程,性能更高
- 时间戳验证:通过时间戳验证数据是否被修改
- 不可重入:StampedLock是不可重入的
4. 锁的优化策略
4.1 自旋锁 vs 适应性自旋锁
java
// 自旋锁实现
public class SpinLock {
private AtomicReference<Thread> owner = new AtomicReference<>();
public void lock() {
Thread current = Thread.currentThread();
while (!owner.compareAndSet(null, current)) {
// 自旋等待
}
}
public void unlock() {
Thread current = Thread.currentThread();
owner.compareAndSet(current, null);
}
}
4.2 锁粗化
JVM会自动将连续的锁操作合并为一个,减少锁获取/释放的开销。
4.3 锁消除
JVM通过逃逸分析,识别出不可能被共享的对象,从而消除不必要的锁。
5. 锁的公平性选择
5.1 公平锁 vs 非公平锁
java
// 公平锁示例
ReentrantLock fairLock = new ReentrantLock(true);
// 非公平锁示例(默认)
ReentrantLock unfairLock = new ReentrantLock(false);
5.2 性能差异
- 公平锁:保证线程按FIFO顺序获取锁,但吞吐量较低
- 非公平锁:允许线程插队,吞吐量更高
6. 锁的性能监控与调优
6.1 使用JConsole监控锁竞争
java
// 获取锁竞争信息
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true);
for (ThreadInfo info : threadInfos) {
if (info.getLockName() != null) {
System.out.println("线程 " + info.getThreadName() +
" 等待锁: " + info.getLockName());
}
}
6.2 锁调优建议
- 选择合适的锁类型:根据读写比例选择ReentrantLock或ReadWriteLock
- 减小锁粒度:将大锁拆分为多个小锁
- 避免锁嵌套:减少死锁的风险
- 使用无锁数据结构:如ConcurrentHashMap
7. 实际应用场景
7.1 缓存系统
java
public class Cache<K, V> {
private final Map<K, V> cache = new HashMap<>();
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
public V get(K key) {
rwLock.readLock().lock();
try {
return cache.get(key);
} finally {
rwLock.readLock().unlock();
}
}
public void put(K key, V value) {
rwLock.writeLock().lock();
try {
cache.put(key, value);
} finally {
rwLock.writeLock().unlock();
}
}
}
7.2 计数器实现
java
public class Counter {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
8. 总结
本文深入分析了JUC中各种锁的底层原理,包括:
- ReentrantLock:基于AQS的可重入锁,支持公平/非公平模式
- ReadWriteLock:读写分离,适合读多写少场景
- StampedLock:乐观读机制,性能更高
- 锁优化策略:自旋、粗化、消除等技术
理解这些锁的底层原理有助于我们在实际开发中做出更好的技术选择,编写出高性能的并发程序。记住,没有最好的锁,只有最适合的锁。
感谢读者观看