ReentrantLock可重入锁原理
Posted wen-pan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ReentrantLock可重入锁原理相关的知识,希望对你有一定的参考价值。
可重入锁原理
上篇 介绍了可重入锁的加锁解锁原理和源码,这里记录一下
ReentrantLock
是如何实现可重入锁的。
①、可重入锁示例代码展示
在main方法中调用了m1方法,m1方法执行时需要获取lock,并且m1方法中又调用了m2方法,m2方法执行仍然需要获取锁。
@Slf4j
public class ReentrantTest {
private final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
ReentrantTest test = new ReentrantTest();
test.m1();
}
public void m1() {
lock.lock();
try {
log.info("我是m1方法....");
// 调用m2方法
m2();
} finally {
lock.unlock();
}
}
public void m2() {
lock.lock();
try {
log.info("我是m2方法....");
} finally {
lock.unlock();
}
}
}
②、可重入锁加锁原理
由于这部分比较简单,就直接上源码了!仍然以非公平锁为例。
public void lock() {
sync.lock();
}
static final class NonfairSync extends Sync {
final void lock() {
// 通过cas的方式将state从 0 设置为 1 (cas是原子性操作,并发时只有一个线程会执行成功)
if (compareAndSetState(0, 1))
// 如果state设置成功,则设置该锁被当前线程独占(将当前线程标记为持有锁的线程)
setExclusiveOwnerThread(Thread.currentThread());
else
// 获取锁失败的后续操作
acquire(1);
}
}
这时候锁已经被调用m1方法的线程占用了,所以调用m2方法的时候再次获取锁的时候,肯定会进入else的逻辑,如下
public final void acquire(int arg) {
// 再次尝试获取锁,如果还是没有获取到(即 tryAcquire(arg) 返回为false),则将该线程加入等待队列
// tryAcquire(arg) : 尝试获取锁
// addWaiter(Node.EXCLUSIVE) : 添加到阻塞链表中
// acquireQueued : 将该节点挂起
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
// ================================可重入锁核心逻辑================================
final boolean nonfairTryAcquire(int acquires) {
// 入参 acquires = 1
// 获得当前正在运行的线程
final Thread current = Thread.currentThread();
// 获取state变量的值,即当前当前锁被重入的次数
int c = getState();
// 1、如果c=0,则证明当前锁没有被任何线程持有(此时state被调用m1方法的线程占用,这里肯定是1)
if (c == 0) {
// 尝试自己通过CAS获取锁
if (compareAndSetState(0, acquires)) {
// 如果获取锁成功,则将当前线程设置为持有线程
setExclusiveOwnerThread(current);
// 返回获取锁成功
return true;
}
}
// 2、如果c != 0 并且 exclusiveOwnerThread = 当前线程,说明当前持有锁的是自己(该处对应重入锁)
else if (current == getExclusiveOwnerThread()) {
// 将state加一(即将重入次数加一)
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置state新值
setState(nextc);
// 返回锁的获取结果
return true;
}
// 否则返回获取锁失败
return false;
}
可以看到,首先判断state是否等于0,如果不是则再判断当前占用锁的线程是不是自己,如果是的话那么就将state的值++。表示该锁又被重入了一次。到这里可重入锁的加锁流程就完毕了。
③、可重入锁的解锁原理
从上面的可重入锁的加锁流程中我们知道了,当一个线程多次去获取同一把锁的时候,他会将state的值加一,表示该锁被重入的1次。那么解锁的时候每一次解锁必然也需要对应的将state的值–,直到state的值为0的时候才说明该锁被完全释放,其他线程才能继续争抢锁。
// 释放锁
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
// 释放锁
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 唤醒等待队列中的第一个线程
unparkSuccessor(h);
return true;
}
return false;
}
// 释放锁核心逻辑
protected final boolean tryRelease(int releases) {
// 计算更新的state值(这里入参 releases = 1)
int c = getState() - releases;
// 释放锁的时候如果判断到当前持有锁的线程不是自己,则抛出异常(自己只能释放自己加的锁)
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果c==0,则说明需要释放锁,直接将free置为true,并且设置当前独占线程为空
// 支持重入锁,只有state == 0,锁才能算释放成功。
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 更新state状态(设置重入次数标志)
setState(c);
return free;
}
从上面的源码中可以看到,每次释放锁的时候都会调用 tryRelease
方法,该方法每次都会将state的值减一,当state的值减为0的时候才表示该锁被完全释放,并且唤醒等待队列中的下一个等待线程进行竞争锁。
④、总结
加锁
- 加锁的时候如果通过cas的方式设置state = 1失败,则说明锁当前已经被占用了。
- 那么检查占用该锁的线程是不是自己
- 如果是则将state的值加一,表示该锁被重入的次数。加锁成功!
- 如果不是,则加锁失败
释放锁
每次释放锁的时候,都将state的值减一,直到state的值被减为0才表示释放锁成功,然后唤醒等待队列中第一个等待的线程进行竞争锁。
以上是关于ReentrantLock可重入锁原理的主要内容,如果未能解决你的问题,请参考以下文章
Java并发-- ReentrantLock 可重入锁实现原理2 - 释放锁