ReentrantLock分析
1,ReentrantLock为可重入互斥锁,性能上与JDK1.6后的synchronized差不多,后者为隐士加锁,并且在JDK1.6引入了锁升级概念,由偏向锁升级为轻量级锁,最后升级为重量级锁,且不能锁降级
2,synchronized是JVM层面的实现,C++代码,比如重量锁基于ObejctMoniter,支持非公平。ReentrantLock基于AQS实现,这些实现逻辑都在sync中,ReentrantLock本身实现了Lock接口,支持公平和非公平模式,构造方法中通过参数fair设置
3,ReentrantLock没有对ConditionObject做额外的实现,直接用AQS的
文章目录
前言
1,state是CAS中内置的volatile变量,为0时则没有线程获取到锁资源
2,默认非公平锁,外部线程与AQS同步队列中被唤醒的线程都抢占锁。公平锁则都从队列中排队获取锁资源
3,AQS内置的同步队列是双向链表。ConditionObject则是单向链表
4,很多核心逻辑ReentrantLock没有重写,直接用的AQS的
以下是本篇文章正文内容,下面案例仅供参考
一、分析加锁流程
1,分析lock方法
public void lock() {
// 有公平实现(FairSync)和非公平实现(NonFairSync)
sync.lock();
}
// 非公平实现
final void lock() {
// 使用CAS,将state从0修改为1
if (compareAndSetState(0, 1))
// 获取到了锁资源,将当前线程赋值给exclusiveOwnerThread变量(表示谁拿到了锁资源)
setExclusiveOwnerThread(Thread.currentThread());
else
// 获取锁失败,执行acquire获取锁
acquire(1);
}
// 公平实现
final void lock() {
// 获取锁,如果获取失败,进入AQS同步队列的尾部
acquire(1);
}
获取锁的过程,就是CAS state的过程,并且设置了变量来保存当前是哪个线程拿到了锁资源。
非公平模式获取锁:直接CAS状态(获取锁),CAS失败
执行acquire方法,如果锁没有被占用再次尝试CAS,如果锁被占用则判断是否是当前线程占用,如果是修改锁重入次数。没有获取到锁时会进入AQS同步队列中,执行LockSpport.part进入休眠状态
公平模式获取锁:执行acquire,步骤和非公平锁只有一处不一样:锁未被占用时会判断是否轮到自己获取锁(前面是否有排队的),前面没有排队的才获取
2,分析acquire方法
公平锁和非公平锁都调用了该方法
public final void acquire(int arg) {
// 尝试获取锁资源
if (!tryAcquire(arg) &&
// 仍然没有拿到锁资源
// addWaiter: 将当前包装成Node,追加AQS同步队列尾部(EXCLUSIVE表示排他模式)
// acquireQueued: 如果当前节点是head,再次尝试获取锁,如果仍然拿不到,线程会被挂起(WAITING)
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// Thread.currentThread().interrupt() 设置中断标志位(只是设置,不一定会终止)
selfInterrupt();
}
// 将没有获取到锁的线程添加到队列尾部
private Node addWaiter(Node mode) {
// 将当前线程封装成Node
Node node = new Node(Thread.currentThread(), mode);
// 尾节点tail为当前节点的pred
Node pred = tail;
// 如果尾节点不为null,说明队列已经有排队的Node
if (pred != null) {
node.prev = pred;
// 将当前节点设置tail节点(尾部追加)
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 尾节点为空或者compareAndSetTail CAS失败,在这里死循环执行
// tail为null则死循环执行compareAndSetHead
// 反之,死循环执行compareAndSetTail
enq(node);
return node;
}
// tryAcquire获取锁失败后,将线程放入队列尾部,然后执行该方法
final boolean acquireQueued(final Node node, int arg) {
// 获取锁是否失败
boolean failed = true;
try {
boolean interrupted = false;
// 死循环
for (;;) {
// 获取当前节点的上一个节点,没有就抛异常
final Node p = node.predecessor();
// 如果上一个节点是head,再次尝试获取锁
// head内部的thread为空,这是一个伪节点,所以此处当前节点是第一个等待获取锁资源的线程
if (p == head && tryAcquire(arg)) {
// 获取锁资源成功,将当前节点指向head,通知置空内部的thread和pre
// 相当于将原来的head脱离出队列,等待被回收
setHead(node);
p.next = null; // help GC
// 获取锁未失败
failed = false;
// 出口
return interrupted;
}
// 当前节点不是第二个节点 || 获取锁又失败了
// 判断当前线程是否可以挂起(LockSupport.park(this),进入WAITING)
if (shouldParkAfterFailedAcquire(p, node) &&
// 执行LockSupport.park(this),挂起线程
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
// 取消节点的处理
cancelAcquire(node);
}
}
// 判断node节点是否可以被挂起
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// 如果上一个节点的状态是-1,则该节点还在排队中
if (ws == Node.SIGNAL)
return true;
// 如果上一个节点状态是1(相当于该节点被取消,这样的节点是无效节点会从队列中移除)
if (ws > 0) {
do {
// 当前节点的上一个 = 上一个节点的上一个,相当于指针绕过了中间的节点
node.prev = pred = pred.prev;
// 一直循环,直到找到有效的pre节点(状态不为1)
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 节点状态不是-1,也不是1,将pred 节点状态设置成-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
// 不能挂起
return false;
}
3,分析tryAcquire方法
公平实现和非公平实现方法不一样,但大致逻辑差不多,公平只是多加了公平获取锁方面的校验
// 非公平实现
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取当前锁状态
int c = getState();
// 锁状态为0,说明没有被占用,可能已经被其他线程释放了
if (c == 0) {
// CAS状态,获取锁
if (compareAndSetState(0, acquires)) {
// 获取锁完成,设置ExclusiveOwnerThread
setExclusiveOwnerThread(current);
return true;
}
}
// 锁被占用了,判断是否是当前线程占用的(ReentrantLock是可重入锁)
else if (current == getExclusiveOwnerThread()) {
// 增加锁的重入次数
int nextc = c + acquires;
// 重入次数不能超过int的边界
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置锁状态
setState(nextc);
return true;
}
return false;
}
// 公平实现
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取当前锁状态
int c = getState();
// 锁状态为0,说明没有被占用,可能已经被其他线程释放了
if (c == 0) {
// 判断是否有线程排在里面当前线程的前面,如果没有,才会去获取锁
if (!hasQueuedPredecessors() &&
// 获取锁
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;
}
// 公平锁:判断当前线程是否可以获取锁,返回false说明前面没有线程排队
public final boolean hasQueuedPredecessors() {
// 尾节点
Node t = tail;
// 头节点
Node h = head;
// 头节点的下一个节点
Node s;
// 头 != 尾
return h != t &&
// 头的下一节点不为null || 头的下一节点不是当前线程(说明前面有排队的)
((s = h.next) == null || s.thread != Thread.currentThread());
}
4,分析tryLock方法
与上面的逻辑基本一致
public boolean tryLock() {
// 不区分公平模式还是非公平模式,都会走非公平逻辑
return sync.nonfairTryAcquire(1);
}
// 非公平模式获取锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 锁没有被占用,CAS获取锁
if (c == 0) {
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");
setState(nextc);
return true;
}
return false;
}
// 待有时间的获取锁
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
// 将等待时间转成纳秒
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
// 线程被打断时抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 尝试获取锁
return tryAcquire(arg) ||
// 没有获取到时,执行带有等待时间的“tryLock”
doAcquireNanos(arg, nanosTimeout);
}
// 带有等待时间的获取锁
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
// 没有等待时间,获取锁失败
if (nanosTimeout <= 0L)
return false;
// 计算锁的结束时间
final long deadline = System.nanoTime() + nanosTimeout;
// 将当前线程线程封装成Node,追加到队列尾部,并返回当前Node
final Node node = addWaiter(Node.EXCLUSIVE);
// 是否获取锁失败
boolean failed = true;
try {
// 死循环
for (;;) {
// 获取当前node的上一个节点
final Node p = node.predecessor();
// 上一节点是head则到了自己获取锁的后,如果获取成功,将当前node设置为head,旧head等待回收
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
// 计算剩余获取锁的时间
nanosTimeout = deadline - System.nanoTime();
// 没有时间了
if (nanosTimeout <= 0L)
return false;
// 判断是否可以挂起线程(WAITING)
if (shouldParkAfterFailedAcquire(p, node) &&
// 可以挂起,判断剩余时间是否大于1000纳秒,给LockSupport.parkNanos留出一定的执行时间
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
// 线程被WAITING时,可以通过设置中断标志位唤醒,这种方式直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
// 取消node的处理
cancelAcquire(node);
}
}
lockInterruptibly获取锁与上面类似,不在分析,区别在于获取锁时,获取不到则一直获取,除非设置了中断标志位
二、分析释放锁流程
1.分析unlock方法
代码如下(示例):
// 释放锁,不区分公平模式和非公平模式
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
// 尝试释放锁,如果重入次数递减后不为0,说明仍然持有锁资源,此时为false
if (tryRelease(arg)) {
// 获取head
Node h = head;
if (h != null && h.waitStatus != 0)
// 唤醒后面的线程
unparkSuccessor(h);
return true;
}
return false;
}
// 尝试释放锁
protected final boolean tryRelease(int releases) {
// 获取锁的重入次数
int c = getState() - releases;
// 当前没有持有锁资源吗,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 锁是否被完全释放
boolean free = false;
// 重入次数等于0,说明全部释放了
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
// 获取node节点的状态
int ws = node.waitStatus;
if (ws < 0)
// 如果节点转状态为-1,设置为0(初始化状态)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
// 如果没有下一节点 || 或者节点状态为1(意味着被设置为取消)
if (s == null || s.waitStatus > 0) {
s = null;
// 从尾部遍历,找到正常的节点
// 因为新增节点时,指针会先指向链表的尾部,关系还没有建立完整,无法正序找到这个刚刚建立的node
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 唤醒线程
LockSupport.unpark(s.thread);
}
总结
待总结。