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 - 释放锁

Java并发包4--可重入锁ReentrantLock的实现原理

ReentrantLock可重入锁在我们的代码中。

java可重入锁(ReentrantLock)的实现原理

ReentrantLock可重入锁的原理及使用场景

Java 重入锁 ReentrantLock 原理分析