ReentrantLock 的使用非常简单,ReentrantLock 是 Java 并发包(java.util.concurrent.locks)中的一个可重入互斥锁实现,它提供了与 synchronized 关键字类似的同步功能,但更加灵活和强大。
核心实现机制
ReentrantLock 的核心是基于 AQS (AbstractQueuedSynchronizer) 实现的:
ReentrantLock 的相关类图如下图所示
Sync是 ReentrantLock 的 内部类:继承自 AQS,是公平锁和非公平锁的基类
NonfairSync:非公平锁实现,ReentrantLock 的默认锁实现
FairSync:公平锁实现

默认是非公平锁可从其空参构造方法来体现

ReentrantLock 的加锁解锁可从以下代码示例体现,只需要 lock,unlock即可实现加锁解锁
public static void main(String[] args) {
// 可重入锁
ReentrantLock lock = new ReentrantLock();
new Thread(() -> {
lock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName());
lock.unlock();
}).start();
new Thread(() -> {
lock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName());
lock.unlock();
}).start();
}
ReentrantLock 加锁过程,实际是调用其同步器sync.lock来实现的

Sync 的加锁有两个实现,非公平锁是直接执行CAS操作,如果CAS操作成功,则设置的当前线程独占,加锁失败则执行 acquire(1);方法

公平锁不同的是lock执行 acquire(1);操作

acquire 是AQS中的操作,会先调用 tryAcquire的子类实现,如果tryAcquire失败了,则将当期线程加入到等待队列中

在tryAcquire时,非公平锁和公平锁也有不同的实现,以下是非公平锁的实现
1、获取到当前锁的状态,如果为0,说明是未加锁的状态,即立马尝试 CAS操作
2、加锁成功,设置当前线程独占该锁
3、如果状态不等于0,则判断当前线程是否是持有锁的线程
4、如果是当前线程占有锁,则进行锁重入
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 如果获取到当前锁的状态为0,说明是未加锁的状态,即立马尝试 CAS操作
if (compareAndSetState(0, acquires)) {
// 加锁成功
setExclusiveOwnerThread(current);
return true;
}
}
// 判断当前线程是否是持有锁的线程
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 如果是,则 state+1,即是锁的状态 + 1,表示当前线程的加锁次数,以此来实现锁的重入
setState(nextc);
return true;
}
return false;
}
公平锁加锁实现
1、判断队列中是否有等待锁的线程,如果没有则尝试获取锁的CAS操作,如果有则执行后面的代码
2、进行锁重入判断
3、如果有等待锁的线程,持有锁的不是当前线程,则返回false
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 判断是否有等待锁的线程
if (!hasQueuedPredecessors() &&
// 如果没有,则尝试获取锁的CAS操作
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;
}
如果尝试获取锁失败了,则将当前线程加入等待锁的队列中,加入队列成功之后,则将当前线程设置中断
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
// 如果尝试获取锁失败了,则将当前线程加入等待锁的队列中
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
非公平锁的特点
1、插队机制:新来的线程不检查等待队列,直接尝试获取锁,如果此时恰好锁可用,就能立即获取,无需排队,这种设计提高了吞吐量,但可能导致某些线程饥饿
2、与公平锁的区别:公平锁的 lock() 方法直接调用 acquire(1),没有这个"插队"尝试,公平锁会严格按请求顺序分配锁
排队等待(acquireQueued)
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
排队机制:
1、自旋检查:不断检查自己是否成为头节点
2、获取机会:当成为头节点时再次尝试获取锁
3、阻塞管理:shouldParkAfterFailedAcquire:设置前驱节点的waitStatus为SIGNAL(-1),parkAndCheckInterrupt:调用LockSupport.park()阻塞线程
4、中断处理:记录中断状态但不立即响应
5、渐进式阻塞:不会立即阻塞线程,经过多次尝试失败之后才真正park
6、进入自旋之后,还是会判断自己是否成为了头结点,如果是头节点,则会tryAcquire尝试获取锁,第一次获取失败之后,会执行shouldParkAfterFailedAcquire,将前置节点的状态设置为 SIGNAL,然后返回 false,进入下一次循环tryAcquire尝试获取锁,失败之后再次shouldParkAfterFailedAcquire,这次执行判断前置节点状态已经为 SIGNAL,则返回true,则自身进入park 等待阶段
解锁源码分析
直接调用 sync.release 的方法
public void unlock() {
sync.release(1);
}
先尝试 release,如果 release成功,则唤醒队列中的头结点的后继节点,这里可以在 unparkSuccessor里面的源码可见,并返回true
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
获取状态,即锁次数,实现重入锁的解锁,如果解锁之后 state=0,那么设置占有线程为 null
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;
}
对比而来,解锁的逻辑要比加锁的逻辑简单的多
unparkSuccessor源码分析
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 获取头节点的后继节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 唤醒后继节点的线程
LockSupport.unpark(s.thread);
}

3283

被折叠的 条评论
为什么被折叠?



