ReentrantLock 源码解析

前言

注:本文的源码来自 JDK11

ReentrantLock 是 Java 中的一个可重入锁,它可以用于替代传统的 synchronized 关键字来进行线程同步。
下面是与 synchronized 关键字的一些对比:

名称实现重入性中断性公平性是否支持超时释放锁
ReentrantLockJava API级别,基于AQS实现可重入可中断支持非公平与公平可以超时手动是否锁
synchronizedJVM 级别,基于对象的 monitor 对象实现可重入不可中断非公平无法设置超时自动是否锁

可以看出 ReentrantLock 提供了更多的灵活性和可扩展性,不知你是否开始对它的原理产生兴趣?

注意:你需要对 AQS 的工作原理有所了解,因为 ReentrantLock 是在 AQS 的基础上实现的。

类结构

ReentrantLock 类实现了 java.util.concurrent.locks.Lock 接口,它的内部实现包括一个 Sync 内部类,该类是ReentrantLock 的核心实现。
Sync 继承了 AbstractQueuedSynchronizer 抽象类,用于管理线程的同步状态。Sync 类有两个子类,分别是NonfairSyncFairSync,用于实现非公平锁和公平锁。UML 如下图所示:

Reentrant lock uml

Sync 类源码

由于公平锁与非公平锁是 Sync 的子类,那么我们先分析 Sync

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    /**
     * 默认的非公平实现,有个细节是,为什么非公平的实现要放父类这里?
     * 其实 ReentrantLock 的 tryLock() 就是直接调这里的,不管你是哪个实现,都是使用非公平的实现。
     */
    @ReservedStackAccess
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        // 如果 state 为 0 表示没有线程占用锁
        if (c == 0) {
        	// cas 成功表示拿到锁
            if (compareAndSetState(0, acquires)) {
            	// 设置当前获取锁的线程
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 因为是可重入锁,当 state 不为 0 时,再判断下如果为当前线程,则将 state 加上去,释放锁的时候再减
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            // 从这里可以知道,可重入次数是有限制的,即为 2147483647
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            // 由于是自己线程,不可能存在竞争关系,没必要用 cas
            setState(nextc);
            return true;
        }
        return false;
    }

    /**
     * 释放锁的逻辑,非公平与公平的都是一样的,因为已经获取到锁,因此这里的代码都线程安全的,都不用 cas
     */
    @ReservedStackAccess
    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;
    }
    // 略...
}

FairSync(公平锁源码)

加锁

下面先来看公平锁的加锁逻辑,这里只需实现 tryAcquire,因为 父类 Sync 已经实现了 tryRelease 方法。

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
    /**
     * 公平锁的 tryAcquire 方法,这里跟非公平的实现几乎一样,只不过多了 hasQueuedPredecessors() 的判断
     */
    @ReservedStackAccess
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
        	// hasQueuedPredecessors() 表示在当前线程之前,队列里是否还有线程等待
        	// true:  head --> node(thread1) --> node(currenThread) 
        	// false: head --> node(currenThreaad)
        	// 因为必须保证公平,所以只需要按照 AQS 的 FIFO 队列来就好了,当前线程没有在队头就获取不到锁返回 false
            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;
    }
}
解锁

解锁直接使用父类的,因此不用重写。

NonfairSync(非公平锁源码)

加锁

下面先来看非公平锁的加锁逻辑,非常的简洁。

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    
    protected final boolean tryAcquire(int acquires) {
    	// 直接使用父类的方法,在上面已经分析过了,请拉回去看看
        return nonfairTryAcquire(acquires);
    }
}
解锁

同样的解锁直接使用父类的,也不用重写。

为什么非公平锁的效率比公平锁的高?

很多时候都不推荐使用公平锁的方式去加锁,都说使用非公平锁的效率要比公平锁的要好,但是为什么呢?
从上面的源码来看,公平锁也仅仅是多了 hasQueuedPredecessors() 的判断,其实在唤醒队列中阻塞的线程时涉及到上下文切换,这需要一定的时间消耗,多个线程的耗时累积起来效率自然就低了。
要尽量地减少这个上下文切换的时间也很简单,直接让当前的线程去抢锁,因为当前线程就在用户态,不会有上下文切换这个耗时,效率自然就好了。

总结

本文对比了 ReentrantLocksynchronized 的区别,知道 ReentrantLock 在用法上更加的灵活;分析了ReentrantLock 公平锁与非公平锁的实现,发现代码是非常简单的,这都得益于 AQS 这个抽象同步框架,其实它们的主要区别是在加锁的时候会不会尝试获取锁;最后思考了为何非公平锁的效率会比公平锁的高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值