相关性质和条件变量-ReentrantLock详解(2)-AQS-并发编程(Java)

news2025/7/5 21:44:35

文章目录

    • 1 可重入
    • 2 可打断
    • 3 公平锁
    • 4 条件变量
      • 4.1 await()
        • 4.1.1 主方法
        • 4.1.2 addConditionWaiter()
        • 4.1.3 isOnSyncQueue()
        • 4.1.4 checkInterruptWhileWaiting()
      • 4.2 signal()
        • 4.2.1 主方法
        • 4.2.2 doSignal()
        • 4.2.3 transferForSignal()
    • 5 后记

1 可重入

可重入在加锁中体现代码如下:

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

在状态不为0即被占用时,判断锁持有线程是不是当前线程,如果是的话表示锁重入,状态计数+1。

在释放锁体现代码如下:

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

如果状态计数-1不为0说明有锁重入,重新设置锁状态计数,而不是直接把锁持有线程设置为null。

2 可打断

ReentrantLock默认实现是非公平锁,为不可打断模式,代码在之前的分析加锁和解锁的时候有提及,这里在详细说明下。

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())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

在加锁过程中如果竞争不到锁,会执行到parkAndCheckInterrupt()方法的时候同park()方法阻塞。在阻塞的过程有如果被打断,parkAndCheckInterrupt()返回true,interrupted打断标记置为true。该线程绑定的节点仍然存在与队列中,除非竞争到锁的时候,也仅仅是返回interrupted值true,然后才会执行自我打断,这种称为不可打断模式。

下面我们来看下可打断是如何实现的?

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

线程阻塞在队列时候,如果被打断,parkAndCheckInterrupt()返回true,doAcquireInterruptibly()方法直接抛出异常InterruptedException,这种称为可打断模式。

3 公平锁

如果不清楚公平锁和非公平锁概念,可以查看上一篇文章或者自行查阅相关文档。默认实现是非公平锁,这里我们对比分析下,它是如果保证“公平",在加锁过程中体现的。

非公平锁:

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

在尝试加锁的时候,如果锁状态计数state==0

  • 非公平锁:通过cas方式把state由0置为1,不会检测队列中是否有其他等待的线程。
  • 公平锁:优先检测队列中是否有阻塞等待的其他线程

具体检测方法hasQueuedPredecessors()代码如下:

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    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());
}

竞争锁的前提是队列为空或者队列不为空且队列第二个节点绑定的线程不是当前现场,这就公平锁的体现。

4 条件变量

条件变量维护的是一个单向链表的队列,下面我们主要讲解下调用await()在队列等待和当条件满足是signal()唤醒。

4.1 await()

示意图如下4.1-1所示:

在这里插入图片描述

分析如下:

4.1.1 主方法

源代码如下:

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

流程如下:

  • 如果线程被中断,直接抛中断异常
  • addConditionWaiter()方法加入队列末尾,返回新创建的节点
  • fullyRelease:释放锁,考虑有锁重入的可能,返回状态
  • 循环判断!isOnSyncQueue()表示该节点是否不在竞争锁的队列中
    • LockSupport.park()阻塞在该条件变量队列,除非被sinal(),sinalAll()或者被中断
    • 被唤醒或者中断情况下,判断是否是被中断,如果是跳出循环
  • acquireQueued()尝试获取锁,否则阻塞在竞争锁队列。如果被打断判断打断模式
  • 如果node.nextWaiter != null,清理已取消的节点
  • 如果打断模式不是0即有被打断,根据打断模式作相应处理

4.1.2 addConditionWaiter()

目标就是插入条件队列尾部。

源代码如下:

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

执行流程如下:

  • 获取队列尾节点t
  • 判断如果不为空且t的等待状态不是Node.CONDITION
    • 清理已取消的节点
  • 已当前线程,Node.CONDITION为参数创建新的节点
    • 也就是说条件队列上节点除非是被取消的其他状态一律是Node.CONDITION
  • 下面执行把新节点链入尾部操作,不在详述

4.1.3 isOnSyncQueue()

目标判断节点是否在竞争锁的队列中。

源代码:

final boolean isOnSyncQueue(Node node) {
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    if (node.next != null) // If has successor, it must be on queue
        return true;
    /*
     * node.prev can be non-null, but not yet on queue because
     * the CAS to place it on queue can fail. So we have to
     * traverse from tail to make sure it actually made it.  It
     * will always be near the tail in calls to this method, and
     * unless the CAS failed (which is unlikely), it will be
     * there, so we hardly ever traverse much.
     */
    return findNodeFromTail(node);
}

执行流程:

  • 判断如果节点状态为Node.CONDITION或者node.pre==null
    • Node.CONDITION只有在新建加入条件队列的节点,所以肯定不在竞争锁的队列
    • node.pre==null 排除哨兵节点,竞争锁队列的节点前驱不能为空
    • 返回false
  • 判断node.next != null,如果有后继节点且在不满足上面条件下,肯定在竞争锁队列中
    • 返回true
  • 最后从竞争锁队列尾部变量队列检查是否在队列中

4.1.4 checkInterruptWhileWaiting()

主要目的是检查什么原因导致阻塞被疏通的,如果是被中断,直接跳出循环。源代码如下:

private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}
final boolean transferAfterCancelledWait(Node node) {
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            enq(node);
            return true;
        }
        /*
         * If we lost out to a signal(), then we can't proceed
         * until it finishes its enq().  Cancelling during an
         * incomplete transfer is both rare and transient, so just
         * spin.
         */
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }

如果被中断,还是会转移该节点到竞争锁队列中,

4.2 signal()

唤醒示意图如下4.2-1所示:

在这里插入图片描述

分析如下:

4.2.1 主方法

源代码如下:

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

流程如下:

  • 如果锁持有线程非当前线程,直接跑一次
  • 条件变量队列不为空,唤醒第一个等待的节点。

4.2.2 doSignal()

源代码如下:

private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

执行流程:

  • 循环执行
    • 头节点指针指向下一个节点
    • 把要唤醒的节点转移至锁竞争队列
      • 失败且下一个节点不为空

4.2.3 transferForSignal()

源代码如下:

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

执行流程如下:

  • cas把节点状态由Node.CONDITION设置为0,失败返回false
  • enq()把节点放入锁竞争队列尾部,具体操作可查看之前ReentrantLock加锁流程分析,返回前驱节点p
  • 判断如果p的等待状态>0(取消)或者cas设置p的等待状态由当前状态为Node.SIGNA失败,直接unpark唤醒节点上绑定的线程
  • 放回true

5 后记

如有问题,欢迎交流讨论。

❓QQ:806797785

⭐️源代码仓库地址:https://gitee.com/gaogzhen/concurrent

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/39637.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

零经验,小白变大厨!

平时煮泡面都会翻车的老王      昨天在朋友圈po了一组美食图      朋友小聚,20分钟搞定一桌菜,嘻嘻。      我点开一看,嚯!      红烧里脊、糖醋排骨、油焖大虾、剁椒鱼头……个顶个的硬菜,而且色泽诱人看起来很好吃的样子,关键是居然20分钟搞定?      难…

2022 高教杯数学建模C题古代玻璃制品的成分分析与鉴别回顾及总结

2022 高教杯数学建模C题古代玻璃制品的成分分析与鉴别回顾及总结 Paper & Code&#xff1a;https://github.com/Fly-Pluche/2022-mathematical-modeling-C 希望可以施舍几个star⭐️ 国赛分工 我们三人都有主要的分工: 队员A主要负责二&#xff0c;三问的求解以及代码的编…

被问到可重入锁条件队列,看这一篇就够了!|原创

本文深入解读了高频面试点——ReentrantLock的条件队列使用方法及其原理。源码有详细注释&#xff0c;建议收藏阅读。点击上方“后端开发技术”&#xff0c;选择“设为星标” &#xff0c;优质资源及时送达Jdk中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantL…

用HTML+CSS做一个简单的新闻门户 1页网页

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 茶文化网站 | 中华传统文化题材 | 京剧文化水墨风书画 | 中国民间年画文化艺术网站 | 等网站的设计与制作 | HTML期末大学生网页设计作业&#xff0c;…

Web前端大作业—电影网页介绍8页(html+css+javascript) 带登录注册表单

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 精彩专栏推荐&#x1f4…

DevOps初学者的指南——阿里出品学习图册带你掌握高薪技术!

开篇 你是否想开始学习DevOps&#xff0c;或者愿意通过增加DevOps这一技能来转变你的职业生涯&#xff1f; 如果你的答案是肯定的&#xff0c;那么你就来对地方了 从初创企业到跨国企业&#xff0c;技术行业的每个细分领域都在改变其软件开发方法。DevOps工具和实践惊人地减…

【mysql 高级】explain的使用及explain包含字段的含义

explain的使用及explain包含字段的含义1.id2. select_type3.table4.type5.possible_keys6.key7.key_len8.ref9.rows10.Extra使用explain关键字可以模拟优化器执行SQL语句&#xff0c;从而知道MySQL是如何处理你的SQL语句的&#xff0c;从而分析你的查询语句或是表结构的性能瓶颈…

面向OLAP的列式存储DBMS-10-[ClickHouse]的常用数组操作

参考ClickHouse 中的数据查询以及各种子句 ClickHouse 数组的相关操作函数&#xff0c;一网打尽 在关系型数据库里面我们一般都不太喜欢用数组&#xff0c;但是在 ClickHouse 中数组会用的非常多&#xff0c;并且操作起来非常简单。ClickHouse 里面提供了非常多的函数&#x…

文本生成视频Make-A-Video,根据一句话就能一键生成视频 Meta新AI模型

Meta公司&#xff08;原Facebook&#xff09;在今年9月29日首次推出一款人工智能系统模型&#xff1a;Make-A-Video&#xff0c;可以从给定的文字提示生成短视频。 Make-A-Video研究基于文本到图像生成技术的最新进展&#xff0c;该技术旨在实现文本到视频的生成&#xff0c;可…

[附源码]Python计算机毕业设计高校第二课堂管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

第十章 二叉树的各种遍历

第十章&#xff1a;二叉树的前、中、后序遍历前期准备:一、前序遍历1、遍历的思路2、遍历代码3、遍历图示二、中序遍历1、遍历的思路2、遍历代码三、后序遍历1、遍历的思路2、遍历代码三、遍历的应用1、计算二叉树中的节点个数2、二叉树叶子节点的个数3、二叉树的深度4、二叉树…

EMC原理-传导(共模、差模)与辐射(近场、远场)详解

目录&#xff1a; 第一章、EMC概念介绍 第二章、感应干扰(近场) 第三章、辐射干扰(远场) 第四章、差模干扰 第五章、共模干扰 ------------------------------------------------------------------------------------------------------------------------ 第一章、EMC…

ceph块存储在线扩容

记录&#xff1a;339 场景&#xff1a;在CentOS 7.9操作系统上&#xff0c;部署ceph-13.2.10集群。应用ceph块设备(ceph block device)&#xff1b;主要是创建ceph块存储和在线扩容相关操作。 版本&#xff1a; 操作系统&#xff1a;CentOS 7.9 ceph版本&#xff1a;ceph-1…

牛顿法(牛顿拉夫逊)配电网潮流计算matlab程序

牛顿法配电网潮流计算matlab程序 传统牛顿—拉夫逊算法&#xff0c;简称牛顿法&#xff0c;是将潮流计算方程组F(X)0&#xff0c;进行泰勒展开。因泰勒展开有许多高阶项&#xff0c;而高阶项级数部分对计算结果影响很小&#xff0c;当忽略一阶以上部分时&#xff0c;可以简化对…

向NS-3添加新模块_ns3.37添加新模块_ns3.37不同版本模块移植

使用ns3的时候&#xff0c;我们需要调用很多模块&#xff0c;比如对wifi的简单功能进行仿真时&#xff1a; ns-3.35_third.cc_ns-3网络仿真工具wifi脚本解析_wifi脚本网络拓扑_ns-3third脚本全注释_基础ns-3_ns-3入门_ns-3third脚本解析_Part1_Mr_liu_666的博客-CSDN博客Intro…

WinSock的I/O模型

目录 一、 套接字的非阻塞工作模式 1.阻塞与非阻塞模式的概念 2.阻塞模式下能引起阻塞的套接字函数 3.两种模式的比较 2. 套接字非阻塞模式的设置方法——ioctlsocket 函数 3. 非阻塞模式下的编程方法 4. 非阻塞模式服务器端程序和客户端程序 二、select模型 1. 套接字…

信息系统综合测试与管理

本文包括软件测试模型、测试技术和测试管理。 一、测试基础 1、软件测试模型 所谓测试模型&#xff08;Test Model&#xff09;&#xff0c;是测试和测试对象的基本特征、基本关系的抽象。 1&#xff09;V模型 V模型实际是软件开发瀑布模型的变种&#xff0c;它反映了测试…

CSDN第十期竞赛

比赛详情&#xff1a; 通过这次的周赛让我受益颇多&#xff0c;这次的题目都是平常练习题目的变形&#xff0c;这次的竞赛是十分有意义的&#xff0c;加强对练习题的强化。 两道模拟题&#xff1a; 目录 1.熊孩子拜访 2.走楼梯 1.熊孩子拜访 题目描述 已知存在一个长度为n的…

FB显示学习期数据不足怎么办?

组合投放广告组和广告系列。组合投放广告组和广告系列有助于加快获得所需成效的速度&#xff0c;这意味着广告投放后很快便可看到稳定的成效。 扩大受众群。受众越多&#xff0c;用户完成您的优化事件的机会越多。 提高预算。如果您的预算过低&#xff0c;无法获得约 50 个​…

47、泛型

一、引入 1、传统方法&#xff1a; package generic_;import java.util.ArrayList; SuppressWarnings({"all"}) public class Generic01 {public static void main(String[] args) {ArrayList arrayListnew ArrayList();arrayList.add(new Dog("旺财",10)…