ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java并发编程之AbstractQueuedSynchronizer队列同步器与可重入锁ReentrantLock

2022-06-19 23:05:16  阅读:203  来源: 互联网

标签:Node 结点 Java int ReentrantLock AbstractQueuedSynchronizer 公平 return final


前言:之前有写过关于重入锁ReentrantLock的解析,而重入锁ReentrantLock的核心在于它的两个锁非公平锁公平锁的所继承的父类AbstractQueuedSynchronizer,接下来就是关于AbstractQueuedSynchronizer的详解,包括图文、源码。后文AbstractQueuedSynchronizer简称AQS
此文相比前文重入锁ReentrantLock多了源码与图解,并且从AQS的角度出发进行解析。

AQS流程图

源码解析

acquire方法源码

根据上面的AQS的执行流程图,AQS先执行acquire()方法。源码如下:

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  1. 分别调用了,tryAcquire()(需要由子类实现);
  2. 调用addWaiter(Node.EXCLUSIVE)以独占模式创建结点Node,并将当先线程作为Nodethread变量,Node加入到AQS的队列中;
  3. addWaiter(Node.EXCLUSIVE)构建好的新Node参数,调用acquireQueued方法。

acquireQueued方法源码

线程队列的等待,由acquireQueued方法进行实现的:

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt()) // 注释1
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
  1. 此方法对应上图中的圆形循环;
  2. 判断前置结点prev是否为头结点head
  3. 不是head结点,调用shouldParkAfterFailedAcquire判断是否应当park等待,并通过parkAndCheckInterrupt方法执行LockSupport.park(this)进行线程的等待,等待已经获得成功的线程release
  4. head结点,则调用tryAcquire方法,获取成功,则将当前结点设置为头结点head,线程获取成功,至此acquire执行完毕。

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

看上述代码,可以发现,是以head结点为参数,调用unparkSuccessor方法:

private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}
  1. 设置等待状态为0 - 初始状态
  2. waitStatus大于0的状态为CANCELLED,意为结点取消,需要将大于0的结点从队列中剔除
  3. 最后,调用LockSupport.unpark(s.thread),以让head的后结点线程取消park方法的阻塞,对应上面acquireQueued方法的注释1
  4. 下面是waitStatus可能出现的值:
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED =  1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL    = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
 * waitStatus value to indicate the next acquireShared should
 * unconditionally propagate
 */
static final int PROPAGATE = -3;

ReentrantLock的公平锁与非公平锁

从AQS的acquirerelease方法可以看到,都调用了tryAcquiretryRelease方法,这两个方法需要由子类实现。最典型的就是可重入锁ReentrantLockNonfairSync(非公平锁)与FairSync(公平锁)实现。

acquire方法的入口之lock()方法

非公平锁NonfairSync

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

公平锁FairSync

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

可以发现,非公平锁在调用acquire方法之前,先调用了compareAndSetState方法,而公平锁是直接调用的acquire


非公平锁与公平锁的tryAcquire实现

非公平锁:

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

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

公平锁:

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

两者都有共同之处:

  • compareAndSetState更新状态
  • 同一线程多次调用lock,会累加state的值,于是也需要对应次数的release

但是公平锁有一个hasQueuedPredecessors判断方法:

public final boolean hasQueuedPredecessors() {
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

从源码不难看出,这里的判断逻辑是,头尾结点不相等,头结点的后结点不为空或者头结点的后结点的线程不为当前线程。简单总结一下就是:队列里排在最前面的结点不是当前线程的结点。这也是公平锁的公平体现之处。

那么,非公平锁又是怎样体现的呢,可以从lock方法看出,在acquire方法之前,先进行compareAndSetState抢锁,这个时候有可能set成功,也有可能失败,失败的话就会进入AQS队列,然后顺序执行,而它也有可能被其它线程捷足先登,所以它是非公平的。

打个比方:

  • 公平锁:排队到食堂吃饭,所有人都严格根据顺序先来后到进行打饭,这个打饭窗口就好比锁,只能按照先来后到的顺序获得锁;
  • 非公平锁:同样排队吃饭,假设这个时候已经排起长队了,而后面每个人来的时候都会趁第一个人不注意进行插队,如果被发现了则乖乖地到后面排队,如果没被发现,就插队成功啦。

标签:Node,结点,Java,int,ReentrantLock,AbstractQueuedSynchronizer,公平,return,final
来源: https://www.cnblogs.com/lcmlyj/p/16391154.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有