一文搞懂重入锁ReentrantLock

265 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 14 天,点击查看活动详情

大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈


前言

重入锁,即ReentrantLock,继承于Lock接口,提供锁重入功能。重入锁与不可重入锁的区别在于,重入锁支持已经获取锁的线程重复对锁资源进行获取。

正文

Java中的synchronized关键字可以隐式的支持锁重入功能,考虑如下一个例子。

public class HelloUtil {

    public static synchronized void sayHello() {
        System.out.print("Hello ");
        sayWorld();
    }

    public static synchronized void sayWorld() {
        System.out.println("World");
    }

}

已知访问由synchronized关键字修饰的静态方法时需要先获取方法所在类的Class对象作为锁资源,所以当A线程调用HelloUtilsayHello() 方法时,需要获取的锁资源为HelloUtil类的Class对象,此时B线程再调用HelloUtilsayHello()sayWorld() 方法时会被阻塞,但是A线程却可以在sayHello() 方法中再调用sayWorld() 方法,即A线程在已经获取了锁资源的情况下又获取了一次锁资源,这就是synchronized关键字对锁重入的支持。

结合上面的例子,已经对重入锁有了直观的认识,下面将分析ReentrantLock是如何实现重入锁的。ReentrantLock的类图如下所示。

ReentrantLock类图

ReentrantLock有三个静态内部类,其中Sync继承于AbstractQueuedSynchronizer,然后FairSyncNonfairSync继承于Sync,因此SyncFairSyncNonfairSync均是ReentrantLock组件中的自定义同步器,且FairSync提供公平获取锁机制,NonfairSync提供非公平获取锁机制。公平和非公平获取锁机制现在暂且不谈,下面先看一下SyncFairSyncNonfairSync实现了哪些方法,如下所示。

Sync类图

由上述可知,ReentrantLock默认使用非公平获取锁机制,然后可以在构造函数中根据传入的fair参数决定使用哪种机制。现在先对上面的讨论做一个小节:ReentrantLock是可重入锁,即已经获取锁资源的线程可以重复对锁资源进行获取,ReentrantLock内部有三个自定义同步器,分别为SyncNonfairSyncFairSync,其中NonfairSyncFairSync能分别提供非公平获取锁机制和公平获取锁机制,具体使用哪一种获取锁机制,需要在ReentrantLock的构造函数中指定。

接下来结合NonfairSyncFairSynclock()tryAcquire() 方法的源码,对非公平获取锁机制和公平获取锁机制进行说明。

NonfairSynclock() 方法如下所示。

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

非公平获取锁调用lock() 方法时会先将stateCAS方式从0设置为1,设置成功表示竞争到了锁,因此非公平获取锁意味着同时获取锁资源时会存在竞争关系,不能满足先到先获取的原则。如果将stateCAS方式从0设置为1失败时,会调用模板方法acquire(),已知acquire() 方法会调用tryAcquire() 方法,而NonfairSynctryAcquire() 方法会调用其父类SyncnonfairTryAcquire() 方法,下面看一下其实现。

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 当前线程如果是获取到锁资源的线程,则将state字段加1
    // 当前线程如果不是获取到锁资源的线程,则返回false然后加入同步队列
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

nonfairTryAcquire() 方法中主要是对获取锁资源的线程进行判断,如果当前线程就是已经获取到锁资源的线程,那么就会将state加1,因为每次都是将state加1,所以可以重复获取锁资源。

接下来再看一下公平获取锁机制的FairSync的实现,首先FairSynclock() 方法会直接调用模板方法acquire(),并已知在acquire() 方法中会调用tryAcquire() 方法,所以这里直接看FairSynctryAcquire() 方法的实现。

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    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;
}

FairSynctryAcquire() 方法与NonfairSync的不同在于当state为0时多了一个hasQueuedPredecessors() 方法的判断逻辑,即判断当前的同步队列中是否已经有正在等待获取锁资源的线程,如果有,则返回true因此公平获取锁意味着绝对时间上最先请求锁资源的线程会最先获取锁,以及等待获取锁资源时间最长的线程会最优先获取锁,这样的获取锁机制就是公平的

现在最后分析一下ReentrantLock的解锁逻辑。无论是非公平获取锁机制还是公平获取锁机制,如果重复对锁资源进行了n次获取,那么成功解锁就需要对锁资源进行n次释放,前(n - 1)次释放锁资源都应该返回falseReentrantLockunlock() 方法会直接调用AbstractQueuedSynchronizer的模板方法release(),并已知在release() 方法中会调用tryRelease() 方法,这里调用的是Sync实现的tryRelease() 方法,如下所示。

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;
}

tryRelease() 方法中每成功释放一次锁资源,就会将state减1,所以当state为0时,就判断锁资源被全部释放,即释放锁资源成功。

总结

重入锁的定义是:已经获取锁资源的线程可以重复对锁资源进行获取

ReentrantLock是重入锁,内部有三个自定义同步器,分别为SyncNonfairSyncFairSync,其中NonfairSyncFairSync能分别提供非公平获取锁机制和公平获取锁机制,具体使用哪一种获取锁机制,需要在ReentrantLock的构造函数中指定。

非公平锁的定义是:同时获取锁资源时会存在竞争关系,不能满足先到先获取的原则

公平锁的定义是:绝对时间上最先请求锁资源的线程会最先获取锁,以及等待获取锁资源时间最长的线程会最优先获取锁,这样的获取锁机制就是公平的


大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 14 天,点击查看活动详情