【碎碎念】公平锁与非公平锁——以ReentrantLock为例

本文详细介绍了Java中ReentrantLock的公平锁与非公平锁的工作原理,通过源码分析发现,无论是公平锁还是非公平锁,线程从等待队列中出队列都是遵循FIFO顺序。公平锁在尝试获取锁时会检查是否有等待线程,而非公平锁则直接尝试获取。两者在效率与公平性上存在权衡,公平锁保证了等待线程的顺序,但可能降低效率;非公平锁提高效率但可能导致某些线程饿死。
摘要由CSDN通过智能技术生成

概念

其实锁的公平与否是人为的以某一参照作为界定,实际就是看设计者对于锁的公平与否是怎样考虑的。

以JDK提供的ReentrantLock为例:

  • 公平锁:当一个新的线程尝试获得锁的时候,会先去判断等待队列中是否有正在等待的线程,如果有,则进入等待等列等待,没有则可以尝试获得锁。
  • 非公平锁:当一个新的线程尝试获得锁的时候,直接尝试获得锁,成功,则执行自己的操作逻辑,失败则进入等待队列中。

这样,公平锁的情况,能够完全保证获得锁的顺序就是队列的顺序,即FIFO。而非公平锁的情况,由于新的线程直接尝试锁的获取,少了一个判断等待队列的状态,减少了线程唤醒的成本,所以效率会比较高。但是也很容易得出,在并发量高的情况下,等待队列中的线程可能永远无法获取锁,即俗称的饿死

纠错

一开始我认为对于等待队列中的线程,设计者会根据公平与否设计不同的出队列操作。即公平锁的时候,等待队列按照等待时间依次获得锁,先到先得,很公平;然后非公平锁就是等待队列中的线程是随机唤醒,体现一个谁运气好,谁获得锁,这很不公平。然而这个想法是错误的。

在自己尝试写测试用例和解读ReentrantLock的源码后,就知道了这样认为是错误的。

源码简单解读

要解释JDK提供的各种锁,那就绕不开AQS(AbstractQueuedSynchronizer),内部维护了我们上面说的等待队列,该队列是CLH(Craig, Landin, and Hagersten)设计的锁队列的一种变形,通常用作自旋锁的实现。AQS底层volatileCAS 实现。

在ReentrantLock中,有两个同步器类实现:FairSyncNonfairSync,前者就是公平锁的关键,后者是非公平锁实现的关键。他们共同继承自类Sync,然后Sync继承了AbstractQueuedSynchronizer。实现公平与否的就在于它们各自重写的方法tryAcquire

FairSync#tryAcquire

可以看到通过hasQueuedPredecessors判断等待队列是否有线程等待

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

NonfairSync#tryAcquire

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

这里调用了父类的方法nonfairTryAcquire

Sync#nonfairTryAcquire

可以看到这里上来就设置锁状态,成功获得锁。

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

当上面返回false,即获得锁失败,那么就会进入等待队列,后续公平锁与非公平锁的逻辑就是一样的,通过自旋的形式尝试获得锁。

可以看到这里判断了前驱节点是否为队头,所以不管是不是公平锁,出队列都是按照队列的顺序,FIFO。

final boolean acquireQueued(final Node node, int arg) {
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    }
}

若文章有误,或你有什么见解,欢迎留言交流指正。
原创不易,若有所帮助,欢迎点赞、收藏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值