我们平时在开发中经常会导入一个包,也就是java.util.concurrent.locks 包,而这个包就是Java并发编程的核心扩展,,提供了比内置synchronized或者valatile关键字更为灵活、强大的锁机制。我们这篇文章就来讲一下这个包下的比较重要的类和接口。
1. ReentrantLock
ReentrantLock是Java并发包java.util.concurrent.locks 下的一个显示锁,什么是显示锁呢?我们来看一下具体的用法:
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 确保最终释放锁
}
}
public int getCount() {
return count;
}
}
我们可以看到,这里我们显示的调用lock,并且最后unlock,这是ReentrantLock锁的特点之一,且这个锁必须要使用在try/finally里以防锁没办法释放,
我们来看一下,ReetrantLock的源码是怎么定义的:
整个ReentrantLock继承了Lock锁,而真正的锁实现其实是sync
而内部实现则是AbstractQueuedSynchronizer,而AQS主要来做线程排队、状态管理、唤醒等逻辑。AQS维护了一个同步状态state(int),以及一个CLH(双向列表)等待队列。然后通过模版方法模式,让子类决定加锁/解锁逻辑。
我们来看一下加解锁的实现: 首先:
public void lock() {
sync.lock();
}
final void lock() {
if (compareAndSetState(0, 1)) // CAS 尝试加锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 加锁失败则进入 AQS 队列
}
在这里就是非公平锁的实现,我们可以看到,首先通过CAS尝试加锁,加锁失败则进入AQS队列。然后setExclusiveOwnerThread是记录当前占用锁的线程。那进入AQS后是如何去唤醒呢?我们来看一下AQS处理释放逻辑:
public final boolean release(int arg) {
if (tryRelease(arg)) {
// 唤醒队列中的下一个线程
unparkSuccessor(head);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException(); // 非持有线程不能释放锁
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
当state递减为0的时候代表完全释放;唤醒AQS队列中等待的下一个线程。
2. ReadWriteLock 和 ReentrantReadWriteLock:读写分离场景
ReadWriteLock锁是读写锁,而ReentrantReadWriteLock是其实现类。在此Java并发包中专为读多写少场景来设计的高性能锁机制,支持读写分离:主要思想就是多线程同时读、写操作必须独占锁(任何读写都互斥)。
用起来很容易,但重要的是具体实现是如何实现的:
2.1 核心设计
2.1.1 状态表示
我们一般使用单个32位整数来表示读写锁的状态:
- 低16位:写锁计数(可重入次数)
- 高16位:读锁持有线程数(不是重入次数)
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT); // 0x00010000
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; // 0x0000FFFF
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 0x0000FFFF
// 计算读锁数量
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
// 计算写锁重入次数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
2.1.2 读锁计数优化
从上面我们可以看到。读写锁的计数其实占挺大的开销,这里我们使用特殊优化:
// 第一个获取读锁的线程
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
// 最近一个获取读锁的线程(缓存)
private transient HoldCounter cachedHoldCounter;
// 其他线程的读锁计数
private transient ThreadLocalHoldCounter readHolds;
static final class HoldCounter {
int count = 0;
final long tid = Thread.currentThread().getId();
}
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
2.2 核心实现Sync类
2.2.1 写锁获取()
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c); // 写锁计数
if (c != 0) { // 锁被占用
// 情况1: 有写锁但当前线程不是持有者 → 失败
// 情况2: 有读锁(写锁为0但有读锁)→ 失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 写锁重入检查
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
// writerShouldBlock 实现公平/非公平策略
if ((w == 0 && writerShouldBlock()) ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
2.2.2 读锁获取
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 写锁被其他线程持有 → 失败
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c); // 读锁数量
// 读锁获取策略(公平/非公平)
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// 读锁计数管理优化
if (r == 0) { // 第一个读锁
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) { // 第一个线程重入
firstReaderHoldCount++;
} else { // 其他线程
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1; // 获取成功
}
// 快速路径失败,进入完整版本
return fullTryAcquireShared(current);
}
2.2.3 公平性实现
公平锁 (FairSync) 与非公平锁 (NonfairSync) 的主要区别在阻塞策略:
// 公平锁实现
static final class FairSync extends Sync {
final boolean writerShouldBlock() {
return hasQueuedPredecessors(); // 检查是否有前驱节点
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors(); // 检查是否有前驱节点
}
}
// 非公平锁实现
static final class NonfairSync extends Sync {
final boolean writerShouldBlock() {
return false; // 写锁总是可以尝试获取
}
final boolean readerShouldBlock() {
// 避免写锁饥饿:检查队列头节点是否是写锁
return apparentlyFirstQueuedIsExclusive();
}
}
2.3 锁降级实现
锁降级是 ReentrantReadWriteLock 的重要特性:
// 锁降级示例
void processData() {
writeLock.lock();
try {
// 修改数据...
// 获取读锁(锁降级开始)
readLock.lock();
} finally {
writeLock.unlock(); // 释放写锁,降级为读锁
}
try {
// 读取数据(仍持有读锁保护)
} finally {
readLock.unlock();
}
}
实现原理:
当线程持有写锁时,可以获取读锁(tryAcquireShared 允许)
释放写锁后,读锁继续保持
保证了数据修改后的一致视图
3. 总结
其实此包里还有一些东西,例如Condition对象,这里就不赘述了,实际上Condition是配合lock使用的一种线程协作工具。这篇文章主要说了其他的锁,其实目的是让大家有更多认识,介绍更加灵活的锁机制,帮助读者在复杂的场景中替代synchronized。