ReentrantLock 源码分析 - 公平锁的 lock() 方法实现

前言

上一篇文章 ReentrantLock 源码分析 - 非公平锁 lock()方法 分析了 非公平锁 的实现方式。

本文接着介绍 公平锁 的具体实现,什么是公平锁和非公平锁请参考

ReentrantLock 源码分析 - 并发编程小知识

正文

知识回顾

AQS:

AQS 是一个双向队列,从队列中的第二个节点开始,节点中的线程才可以参与锁竞争,第一个节点被称为“虚节点” 或者 “哨兵节点”。

非公平性具体体现:

一个线程 A 调用 lock() 方法去抢占锁时, 它并不会考虑 AQS 队列 中是否有其他线程在等待,它会认为我们处于同一起跑线,谁抢到算谁的,但是明显 AQS 队列中的线程来的早,先到先得嘛, 如果刚来的线程 A 先手抢到锁,显然是不公平的。

图解队列的状态

1. AQS 队列初始状态

在这里插入图片描述

2. 当第一个线程节点进入队列

这个过程分为两个步骤:

2.1 初始化一个哨兵节点

new Node(), Node 中的 thread 变量为 null。
在这里插入图片描述

2.2 节点入队

在这里插入图片描述

3. thread1 获得锁之后

在这里插入图片描述

公平锁的 lock() 实现

公平锁的 lock 实现在 FairSync类中,可以看出是调用的 AQSacquire(1) 方法。


        final void lock() {
            acquire(1);
        }

acquire(int arg) 方法前面提到了很多次, 首先是调用 tryAcquire(arg) 方法, 而tryAcquire(arg) 这个抽象方法由继承自 AQSFairSync实现的

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

FairSynctryAcquire 实现方式与上一篇文章 ReentrantLock 源码分析 - 非公平锁 lock()方法 的实现方式大致相同, 只不过加了hasQueuedPredecessors()这个公平策略,我们来后面会着重讲下这个方法。

先从与语义逻辑上看下公平锁的 tryAcquire实现

      protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 如果锁未被其他线程占用
            if (c == 0) {
               // 如果 AQS 队列中, 当前线程前面没有其他线程比他排队更久(公平策略)
               // 并且 CAS 操作成功,就标记独享锁被当前线程占用
                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;
        }.  

hasQueuedPredecessors() 公平策略实现

public final boolean hasQueuedPredecessors() {
       // 头节点=head=h, 尾节点=tail=t
        Node t = tail;
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
}

从语义上理解:

就是当前线程的前驱节点 不是 队列的头节点,就返回 true, 表示有的线程比当前线程等的要久。

分步骤拆解:

  1. 如果 h == t(即 head == tail) 队列为空,表示没有其他线程等待,直接返回 false
    在这里插入图片描述

  2. 如果 h != t && h.next == null, 表示第一个节点 即将入队,也就是头节点的后继指针还未指向,thread1 节点(只能说一只脚迈进了队列),表示 thread1 比当前线程来的早,此时返回 true,
    在这里插入图片描述

  3. 如果 h.next != null, 那么此时队列的状态如下图所示:

在这里插入图片描述
那么此时只能通过 s.thread != Thread.currentThread(),判断 thread1 是否等于当前的线程,如果不是,表示 thread1 比当前线程 来的早,返回 true,否则返回 false。

总结

ReentrantLock 的非公平锁 和 公平锁的主要区别在于 tryAcquire(int arg) 方法实现上。
公平锁在该方法的实现上比非公平锁多加了一个 公平策略,也就是 hasQueuedPredecessors() 方法。

再次强调两点:

  1. 请先熟悉前面的系列文章内容, 再来阅读本文, 前后的文章是相关联的。
  2. 先从代码语义去掌握整个 lock 方法的逻辑流程, 再去看代码的实现,看不懂的可以先略过。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值