深入并发AQS三

深入并发AQS二主要关注锁的申请,这里记录下ReentrantLock对于锁的释放过程。

之前提过当线程释放锁时,会查看CLH队列中是否有线程正在等待,原则上会先释放header结点后的线程结点。

对于ReentrantLock,当前线程释放锁时有两种情况:

 1.当前CLH队列无阻塞线程结点

 2.当前CLH队列有阻塞线程结点

  对于这种情况需要唤醒队列中线程,分以下几个步骤:

第一步:

先执行ReentranLock的tryRelease方法,它会将当前线程状态置减1。如果当前state值为0,则将exclusiveOwnerThread置为NULL。在这里ReentranLock的tryRelease方法可以不加锁,因为释放操作只能由当前拥有这个锁的线程去做。

第二步:

尝试唤醒队列中线程。

对于锁释放操作客户端调用ReentrantLock的unlock时会调用sync.release(1),然后sync会调用父类AQS的release(int arg)方法,这个调用过程如下:

reentrantLock.unlock() => sync.release(1) => AQS的release(int arg)。

接下去看下具体细节,这里因为最终调用的是AQS方法,因此先理解AQS的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;
    }

可以看到跟上述描述的一样,先会调用tryRelease(arg)方法,这个tryRelease(arg)方法在子类NonFairSync类实现。

它的作用就是上述提到的将锁持久状态state减1。如果state减后为0,这时将exclusiveOwnerThread置为NULL。

代码如下:

 protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
 }

这里tryRelease没有用到锁。

接着如果tryRelease返回true表明已经完全释放了锁的时候,返回到AQS的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;
    }

这时会进入if (tryRelease(arg))内部的释放队列中结点的操作。这里先取出header结点。然后如果header结点不为空并且其waitStatus不为0(表明至少有线程在队列中出现),这时调用unparkSuccessor方法,进行释放线程结点操作。

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

这里会先将header结点先置为0(如果当前header没有被取消或者已经是0的状态)。

然后从队列中找出header的下一个结点,有的话执行LockSupport.unpark(s.thread);释放锁。

否则会尝试从tail开始往前找到一个没被取消,不是header结点且非null的结点,将它进行释放。

这样锁释放部分流程已经解析完成。

深入并发AQS三,布布扣,bubuko.com

时间: 2024-12-09 18:52:16

深入并发AQS三的相关文章

深入并发AQS二

AQS需要解决以下几个问题: 1.锁状态,如何保证并发情况下能够安全的更新? 2.当前线程不能获取锁时,放在哪里? AQS是放在一个队列当中 3.如何提高效率? AQS本身有两个核心实现方法acquire及: public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } public final bo

聊聊高并发(三十五)理解内存屏障

在聊聊高并发(三十三)从一致性(Consistency)的角度理解Java内存模型 我们说了硬件层提供了满足某些一致性需求的能力,Java内存模型利用了硬件层提供的能力指定了一系列的语法和规则,让Java开发者可以隔绝这种底层的实现专注于并发逻辑的开发.这篇我们来看看硬件层是如何提供这些实现一致性需求的能力的. 硬件层提供了一系列的内存屏障 memory barrier / memory fence(Intel的提法)来提供一致性的能力.拿X86平台来说,有几种主要的内存屏障 1. ifence

聊聊高并发(三十六)Java内存模型那些事(四)理解Happens-before规则

在前几篇将Java内存模型的那些事基本上把这个域底层的概念都解释清楚了,聊聊高并发(三十五)Java内存模型那些事(三)理解内存屏障 这篇分析了在X86平台下,volatile,synchronized, CAS操作都是基于Lock前缀的汇编指令来实现的,关于Lock指令有两个要点: 1. lock会锁总线,总线是互斥的,所以lock后面的写操作会写入缓存和内存,可以理解为在lock后面的写缓存和写内存这两个动作称为了一个原子操作.当总线被锁时,其他的CPU是无法使用总线的,也就让其他的读写都等

Java并发编程三个性质:原子性、可见性、有序性

并发编程 并发程序要正确地执行,必须要保证其具备原子性.可见性以及有序性:只要有一个没有被保证,就有可能会导致程序运行不正确 线程不安全在编译.测试甚至上线使用时,并不一定能发现,因为受到当时的CPU调度顺序,线程个数.指令重排的影响,偶然触发 线程安全的定义 比如说一个类,不论通过怎样的调度执行顺序,并且调用处不用对其进行同步操作,其都能表现出正确的行为,则这个类就是线程安全的 并发编程三个概念 原子性: 一个操作或多个操作要么全部执行且执行过程不被中断,要么不执行 可见性: 多个线程修改同一

并发编程三要素:原子性,有序性,可见性

并发编程三要素 原子性:一个不可再被分割的颗粒.原子性指的是一个或多个操作要么全部执行成功要么全部执行失败. 有序性: 程序执行的顺序按照代码的先后顺序执行.(处理器可能会对指令进行重排序) 可见性: 一个县城对共享变量的修改,另一个线程能够立刻看到. 一.原子性 线程切换会带来原子性的问题 int i = 1; // 原子操作 i++; // 非原子操作,从主内存读取 i 到线程工作内存,进行 +1,再把 i 写到主内存. 虽然读取和写入都是原子操作,但合起来就不属于原子操作,我们又叫这种为"

C++11 并发指南三(std::mutex 详解)

C++11 并发指南三(std::mutex 详解) 上一篇<C++11 并发指南二(std::thread 详解)>中主要讲到了 std::thread 的一些用法,并给出了两个小例子,本文将介绍 std::mutex 的用法. Mutex 又称互斥量,C++ 11中与 Mutex 相关的类(包括锁类型)和函数都声明在 <mutex> 头文件中,所以如果你需要使用 std::mutex,就必须包含 <mutex> 头文件. <mutex> 头文件介绍 Mu

【Java并发编程实战】—– AQS(三):阻塞、唤醒:LockSupport

在上篇博客([Java并发编程实战]-– AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 lock方法,在调用acquireQueued(): if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; 在acquireQueued()中调用parkAndCheckIn

java并发-AQS.ObjectCondition源码解析

1 什么是条件队列 它使得一组线程能够通过某种方式来等待特定的条件变成真,条件队列的元素是一个个正在等待状态的线程.对象的内置锁(synchronized语义对应的同步机制),关联着一个内置的条件队列.Object的wait/notify/notifyAll等方法构成了内部条件队列的API(即将内部锁与内部条件队列关联的机制). 内部条件队列是需要内置锁保护的,即:需要调用对象X中的条件队列,必须持有对象X上的锁.这是因为状态处于并发环境下,"等待依赖状态的某个条件"与"维护

Java并发AQS原理分析(一)

我们说的AQS就是AbstractQueuedSynchronizer,他在java.util.concurrent.locks包下,这个类是Java并发的一个核心类.第一次知道有这个类是在看可重入锁ReentrantLock中,在ReentrantLock中有一个内部类Sync继承于AbstractQueuedSynchronizer,是ReentrantLock的核心实现.在并发包中的锁几乎都是基于AQS来构建的,但是在看源码的时候就会发现他们并没有直接继承AbstractQueuedSyn