ReentrantLock公平锁的实现原理

类结构

类结构图

image

核心类

AbstractQueuedSynchronizer.acquire()(AQS):提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架。

ReentrantLock,实现了Lock的统一锁接口。锁功能是基于AbstractQueuedSynchronizer实现的。声明的内部类Sync就是继承自AbstractQueuedSynchronizer,是一种模板设计模式

NonFairSyncFairSyncSync的子类,NonFairSync是非公平锁的实现,FairSync为公平锁实现。

Node,是AbstractQueuedSynchronizer的内部类,是AQS队列的实现,每个节点都有前后节点。并且标识了当前的状态,和节点存储的线程。

源码探索

以FairSync公平锁的实现看下lock()方法和unlock()方法的逻辑

lock()

AbstractQueuedSynchronizer.acquire()

FairSync.lock()方法中会调用acquire(1)方法申请锁,这个方法会调用父类AbstractQueuedSynchronizer.acquire()方法。

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

这个方法中**tryAcquire()尝试获取锁,如果失败调用acquireQueued()方法加入排队,并且自旋判断进入阻塞;在加入排队之前调用addWaiter()**方法来为当前线程创建节点,并加入链表中。

AbstractQueuedSynchronizer.addWaiter()

在排队的双向链表中增加节点

private Node addWaiter(Node mode) {
    // 创建当前线程的节点
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    // 如果tail也就是最后一个节点不为空
    if (pred != null) {
        // 将原来的tail节点设置为新节点的上一个节点
        node.prev = pred;
        // 将新节点CAS成tail节点
        if (compareAndSetTail(pred, node)) {
            // 原tail节点的下个节点为新节点
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

private Node enq(final Node node) {
    // 自旋
    for (;;) {
        Node t = tail;
        // 如果tail节点为空,则进行初始化链表
        if (t == null) { // Must initialize
            // 新建一个空节点,并且CAS掉Head节点
            if (compareAndSetHead(new Node()))
                // head节点赋值给tail节点
                tail = head;
        } 
        // 第二次的情况t就不可能为空了。
        else {
            // 当前线程新建的节点的上一节点为head节点
            node.prev = t;
            // 设置当前线程新建的节点CAS为tail节点
            if (compareAndSetTail(t, node)) {
                // 头节点的下一个节点为新建节点
                t.next = node;
                return t;
            }
        }
    }
}

addWaiter的逻辑:

  1. 新建一个当前线程的Node节点。
  2. 如果原tail不为空,则将原tail节点的下一个节点设置成新的Node节点,新Node节点设置成tail节点,并且上级节点为原tail节点,程序然后返回当前节点
  3. 如果原tail为空,则调用**enq()**方法,该方法进行自旋。如果tail节点为空,则进行链表初始化,创建一个空节点为Head节点。将新建的节点设置成tail节点,并且和Head节点简历双向链表

ReentrantLock.FairSync.tryAcquire()

尝试获得锁

protected final boolean tryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 获取当前节点的状态,默认一开始为初始状态,为0;并且state为volatile的
    int c = getState();
    if (c == 0) {
        // hasQueuedPredecessors判断前面是否有排队的;
        // compareAndSetState将同步器状态CAS成1,1为取消状态(也可理解为拿到锁状态),因为拿到锁了,队列节点就取消等待了。
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            // 设置拿到排他锁的线程为当前线程
            setExclusiveOwnerThread(current);
            // 返回获得锁
            return true;
        }
    }
    // 实现重入锁:Node.state不为0,判断拥有锁的线程是否当前线程,如果是那说明是锁重入
    else if (current == getExclusiveOwnerThread()) {
        // 每进入一次则状态 +1
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        // 返回获得锁
        return true;
    }
    // 都不是则没有拿到锁,返回false
    return false;
}

public final boolean hasQueuedPredecessors() {
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    // 是否还存在排队的判断逻辑:
    // head和tail不是同一个,并且head的下一个不为空。并且head的下一个的线程不为当前线程。
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

tryAcquire的逻辑:

  1. 先判断当前节点为初始状态,则判断前面是否还有排队的节点,如果没有则CAS更新当前节点的状态为1(拿到锁状态),并且设置当获取排他锁的线程为当前线程。
  2. 如果状态不为初始状态,则进行重入判断,判断锁线程是否为当前线程。并且在当前节点的状态+1 。
  3. 即不是重入锁也没拿到锁,则返回false

AbstractQueuedSynchronizer.acquireQueued()

将新建的节点加入到队列进行排队,并且将当前节点的线程进入阻塞。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 自旋阻塞
        for (;;) {
            // 获取当前节点的前一个节点
            final Node p = node.predecessor();
            // 如果前一个节点是head节点,则尝试获取锁
            if (p == head && tryAcquire(arg)) {
                // 加锁成功了,将当前节点设置为head节点,将prev置空
                setHead(node);
                // 将原head节点的next置空,脱链帮助GC回收
                p.next = null; // help GC
                failed = false;
                // 返回阻塞为false
                return interrupted;
            }
            // 如果前一个节点不是head节点
            // 进行对前一个节点的状态的判断,来决定当前节点是否应该阻塞,
            if (shouldParkAfterFailedAcquire(p, node) &&
                // 阻塞并检查中断
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        // failed正常情况都为false,除非自旋阻塞最后都没有进到获取锁里面
        if (failed)
            // 取消进行中的锁申请
            cancelAcquire(node);
    }
}

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    // -1 说明前一个节点正常阻塞
    if (ws == Node.SIGNAL)
        // 如果前一个节点阻塞,当前节点就正常阻塞
        return true;
    // 如果前一个节点大于0,则说明已经放弃了。
    if (ws > 0) {
        do {
            // 链表中剔除前一个放弃状态的节点
            node.prev = pred = pred.prev;
            // 如果前一个状态还是大于0,继续往前找。
        } while (pred.waitStatus > 0);
        // 将找到非放弃的节点,设置下一个节点为当前节点
        pred.next = node;
    } else {
        // 如果状态不是阻塞也不是放弃,相当于0或者<-1的状态,将前一个节点状态CAS成-1阻塞。等待唤醒
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

private final boolean parkAndCheckInterrupt() {
    // 进行线程阻塞
    LockSupport.park(this);
    // 此处检查阻塞是否被中断,该方法不仅检查中断还会重置中断标志。
    return Thread.interrupted();
}

acquireQueued()方法逻辑:

  1. 阻塞排队,循环判断尝试获取锁。
  2. 判断当前节点的前一个节点是否是Head节点,如果是说明排队到头了,可以进行锁获取。如果获取到锁将当前节点设置成Head节点(Head节点中thread为null),原Head节点剔除出链表。
  3. 如果前一个节点不是Head节点或者tryAcquire()没有获得锁,则进行阻塞判断并调用LockSupport.park()阻塞。
  4. 阻塞判断方法中,核心逻辑是将链表中当前节点之前的节点放弃的则剔除,排队的置为-1。这个方法返回false,调用的代码会持续循环
  5. 阻塞释放之后,继续循环判断头节点和尝试获取锁,拿到锁之后则返回。

AbstractQueuedSynchronizer.selfInterrupt()

设置当前线程中断标识

static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

在parkAndCheckInterrupt()方法中等待阻塞被释放,则继续循环尝试获取锁。阻塞被释放存在正常释放(unpack())和阻塞中断释放,调用Thread.interrupted()方法判断是否阻塞中断释放,会将线程的中断标志重置调;所以在再次获取锁之后,还要进行线程中断标识的设置

unlock()

AbstractQueuedSynchronizer.release()

释放锁unlock()方法是调用了AbstractQueuedSynchronizer.release()方法

public final boolean release(int arg) {
    // 尝试释放锁
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            // 唤醒下一个排队者
            unparkSuccessor(h);
        return true;
    }
    return false;
}

release()方法主要干了两件事情

1、尝试释放锁
2、释放成功之后,对Head节点检查,唤醒排队的后继者

ReentrantLock.Sync.tryRelease()

释放锁

protected final boolean tryRelease(int releases) {
    // 释放重入锁,每次释放状态-1
    int c = getState() - releases;
    // 如果当前线程,不等于锁所有者的线程,则抛异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 如果c为0,则说明重入锁也都释放完成
    if (c == 0) {
        // 完成释放则返回成功
        free = true;
        // 锁拥有者的线程置空
        setExclusiveOwnerThread(null);
    }
    // 更新当前节点状态
    setState(c);
    return free;
}

tryRelease()方法逻辑:

判断锁持有者是否当前线程。判断当前节点状态-1 是否为0 ;将锁拥有者线程置空,释放锁。如果最后状态不为0,返回释放锁失败

AbstractQueuedSynchronizer.unparkSuccessor()

唤醒下一个后继者

private void unparkSuccessor(Node node) {
    // 当前节点为负数,则将节点状态置为0
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    // 检查下一个节点
    Node s = node.next;
    // 下一个节点为空或者状态大于0
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 循环从尾部往前找,直到找到离当前node最近的一个符合等待状态的节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            // 找到一个节点状态小于0的,说明在等待
            if (t.waitStatus <= 0)
                // 先将等待的节点赋给s
                s = t;
    }
    // 存在下一个节点,则将下一个节点唤醒
    if (s != null)
        // 将下一个排队者唤醒
        LockSupport.unpark(s.thread);
}

unparkSuccessor逻辑:

  1. 获取当前节点的下一个节点,是否满足条件,满足则唤醒。
  2. 不满足则循环从尾部往前找,找到离当前Node最近的一个满足条件的节点。
  3. 将符合条件的排队节点唤醒

Node链表流转情况(流转图)

image

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值