jdk源码-浅析AQS一

https://www.cnblogs.com/leesf456/p/5350186.html

https://www.cnblogs.com/zhanjindong/p/java-concurrent-package-aqs-AbstractQueuedSynchronizer.html

1 AQS

AbstracQueuedSynchronizer简称AQS,很重要!有多重要呢?看看附图就知道了!

在这里插入图片描述

可以毫不夸张的说是AQS是java代码实现锁的基础,更是线程池的基础!

AQS的实现原理是一个先入先出的等待队列!

子类必须定义更改此状态的受保护方法,并定义该状态对于获取或释放此对象而言意味着什么。

2 AbstracQueuedSynchronizer 数据结构

由下示意图,我们简单分析以下内置Class Node的具体结构:

在这里插入图片描述

2.1 node状态

Node结点是对每一个等待获取资源的线程的封装,其包含了需要同步的线程本身及其等待状态,如是否被阻塞、是否等待唤醒、是否已经被取消等。变量waitStatus则表示当前Node结点的等待状态,共有5种取值CANCELLED、SIGNAL、CONDITION、PROPAGATE、0。

在这里插入图片描述

2.2 数据结构

AbstractQueuedSynchronizer类底层的数据结构是使用双向链表,是队列的一种实现形式。其中Sync queue,即同步队列,是双向链表,包括head结点和tail结点,head结点主要用作后续的调度。而Condition queue不是必须的,其是一个单向链表,只有当使用Condition时,才会存在此单向链表。并且可能会有多个Condition queue。

在这里插入图片描述

3 AbstractQueuedSynchronizer

AbstractQueuedSynchronizer继承自AbstractOwnableSynchronizer抽象类,并且实现了Serializable接口,可以进行序列化。

其提供了两个方法设置独占模式线程已经获取独占模式线程。

4 如何上锁?lock

acquire说明—》

请求独占锁,忽略所有中断,至少执行一次tryAcquire,如果成功就返回,否则线程进入阻塞–唤醒两种状态切换中,直到tryAcquire成功。

以ReentrantLock为例,我们来简单了解下上锁的过程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sx3XyRqr-1571312124033)(assets/1569488822650.png)]

  • tryAcquire通过cas操作来尝试占有锁,但是不见得能马上成功;
  • acquire是真正的锁的过程,先继续tryAcquire,不成功则添加节点,加入队列,在循环中挂起,或者继续执行!

5 如何解锁? unlock?

在这里插入图片描述

unlock的过程则相对简单,主要实现就是将独占线程设为null,修改状态,最后就是释放锁!

  • tryRelease为AQS子类实现,具体的状态释放;
  • release则为AQS final实现,主要功能就是调用tryRelease,成功则取消挂起!

事实上关于普通的加锁和去锁,如上两节分析就足够了,在数据结构上也仅用到双向队列,并未涉及conditionQueue通常情况下也是如此!那么ConditionQueue在什么情况被使用呢?且听下节分解!

6 ConditionObject简介

在这里插入图片描述

Condition是对象监视器的替代品,扩展了监视器的语义!

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Condition.html

https://coderanch.com/t/491354/java/await-wait

http://www.iocoder.cn/JUC/sike/Condition/

ConditionObject是AQS中的内部类,提供了条件锁的同步实现,实现了Condition接口,并且实现了其中的await(),signal(),signalALL()等方法。

ConditionObject主要是为并发编程中的同步提供了等待通知的实现方式,可以在不满足某个条件的时候挂起线程等待。直到满足某个条件的时候在唤醒线程。

可以说ConditionObject更加重要,使用条件锁的地方,都会或多或少与该类产生关联,简单看看调用就知道了!

在这里插入图片描述在这里插入图片描述
await(), signal(),signalAll() 的功用和 wait(), notify(), notifyAll() 基本相同, 区别是, 基于 Condition 的 await(), signal(), signalAll() 使得我们可以在同一个锁的代码块内, 优雅地实现基于多个条件的线程间挂起与唤醒操作。

6.1 await源码分析

https://blog.csdn.net/u012420654/article/details/56496631

public final void await() throws InterruptedException {
    if (Thread.interrupted()) {
        // 抛出异常...
    }

    // 创建包含当前线程的节点并添加到[条件等待队列]
    Node node = addConditionWaiter();

    // 释放锁的所有重入计数,失败则中断线程,并将节点的状态置为 CANCELD
    long savedState = fullyRelease(node);
    int interruptMode = 0;

    // 判断该节点是否在[同步等待队列]
    while (!isOnSyncQueue(node)) {
        // 不在的话,则线程进入阻塞状态
        LockSupport.park(this);

        // 表示线程被唤醒的操作:确定中断模式并退出循环
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) {
            break;
        }
    }

    // 成功获取独占锁后,并判断  interruptMode 的值
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE) {
        interruptMode = REINTERRUPT;
    }


    if (node.nextWaiter != null) {
        // 清除条件等待队列上节点状态不为 CONDITION 的节点
        unlinkCancelledWaiters();
    }

    if (interruptMode != 0) {
        // 根据 interruptMode 作出对应的动作
        // 若为 THROW_IE 则抛出异常中断线程
        // 若为 REINTERRUPT 则设置线程中断标记位
        reportInterruptAfterWait(interruptMode);
    }
}

在这里插入图片描述

6.2 signal源码分析

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

7 为什么wait和notify notifyAll需要和Synchronized放在一起?

https://blog.csdn.net/lengxiao1993/article/details/52296220

https://www.cnblogs.com/newlangwen/p/9607837.html

https://blog.csdn.net/javazejian/article/details/72828483

在这里插入图片描述

Marco Ehrentreich写道:
​ 这意味着wait(),notify()与实例的一个唯一的内部锁直接相关,而Condition中的方法可以与多个锁结合使用。 这使您可以使用等待功能并通过多个等待集进行通知。

具有一个锁的多个条件var的情况更为常见(如果事实上,Lock / Condition类不允许将Condition类与多个Lock一起使用)。

典型的情况是在队列上加锁,有两个不同的条件变量:一个用于队列何时为空,等待数据,另一个用于队列何时为满,等待空间。

8 为什么wait和notify是Object方法

简单说法:因为synchronized可以是任意对象所以任意对象可以调用wait和notify

专业说法:方法在操作同步线程时,都必须要标识它们操作线程的锁,只有同一锁的被等待线程可以被同一个锁的notify唤醒,不可以对不同锁的线程进行唤醒,也就是等待和唤醒必须是同一锁,而锁可以是任意对象,所以可以被任意对象调用的方法是定义在object类中。

发布了88 篇原创文章 · 获赞 16 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/xinquanv1/article/details/102613556