JUC并发编程18 | AQS分析

news2024/11/19 3:27:31

尚硅谷(140-155)

18 AQS

前置知识

  • 公平锁和非公平锁
  • 可重入锁
  • 自旋思想
  • LockSupport
  • 双向链表
  • 设计模式——模块设计

18.1 AQS入门级别理论知识

AQS一般指的是 AbstractQueuedSynchronized

AQS 是用来实现锁或者其他同步器组件的公共基础部分的抽象实现,是重量级基础架构以及整个JUC体系的基石,主要用于解决锁分配给谁的问题。

整体就是一个FIFO队列来完成资源获取线程的排队工作,并通过一个int类型变量表示持有锁的状态。

在这里插入图片描述

与AQS有关的:ReentrantLockCountDownLatchReentrantReadWriteLockSemaphore

在这里插入图片描述

既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?

如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是 CLH队列(双向链表) 的变体实现的.将暂时获取不到锁的线程加入到队列中,这个队列就是AQS同步队列的抽象表现。它将要请求共享资源的线程及自身的等待状态封装成队列的结点对象(Node),通过CAS、自旋以及LockSupport.park()的方式,维护state变量的状态,使并发达到同步的效果。

AbstractQueuedSynchronized 部分源码

在这里插入图片描述

  • Node 等待线程的保存位置
  • head 前指针
  • tail 后指针(所以是双向队列)
  • state 线程状态位

在这里插入图片描述

18.2 AQS源码分析前置知识储备

AQS内部体系架构

在这里插入图片描述

18.2.1 AQS 自身

state

  • 0,线程可以进入
  • 大于等于1,有人占用资源,需要等待

CLH 队列

在这里插入图片描述

  • Node 等待线程的保存位置
  • head 前指针
  • tail 后指针(所以是双向队列)

总结:有阻塞就需要排队,实现排队必然需要队列,state 变量 + CLH 双端队列

18.2.2 ASQ 内部 Node 类

static final class Node {
    	// 表示线程以共享的模式等待锁
        static final Node SHARED = new Node();
    	// 表示线程正在以独占的方式等待锁
        static final Node EXCLUSIVE = null;
		
    
    	// 表示当前线程获取锁的请求已经取消了
        static final int CANCELLED =  1;
		// 表示当前线程已经准备好了,就等资源释放了
        static final int SIGNAL    = -1;
		// 表示结点在等待队列中,结点线程等待唤醒
        static final int CONDITION = -2;
		// 当前线程处在SHARED情况下,该字段才会使用,传播
        static final int PROPAGATE = -3;
     	
    	// 当前结点在队列中的状态(上面四种)
        volatile int waitStatus;
		// 前驱指针
        volatile Node prev;
		// 后继指针
        volatile Node next;
		// 表示醋鱼该结点的线程
        volatile Thread thread;
        // 指向下一个处于condition状态的结点
        Node nextWaiter;

        
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    
        }

        Node(Thread thread, Node mode) {
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { 
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

18.3 AQS源码深度讲解和分析

Lock 接口的实现类,基本都是通过【聚合】一个【队列同步器】的子类完成线程访问控制的

ReentrantLock的原理

在这里插入图片描述

默认使用的是非公平锁,传入的是true的话则是公平锁

public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

进入非公平锁

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    final void lock() {
        // 我们希望是没有线程占领资源的,如果没有占领资源,把0置为1
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        // 如果有线程占用,acquire置1,去排队
        else
            acquire(1);
    }

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

公平锁:直接置1

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

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

所以,创建代码的时候

// 非公平
Lock lock = new ReentrantLock();
// 公平
Lock lock = new ReentrantLock(true);
// 非公平
Lock lock = new ReentrantLock(false);

在尝试获得锁的过程中 公平锁和非公平锁,就差异了一个 !hasQueuedPredecessors() ,有没有前驱对象。

如果公平锁中有前驱对象,需要在队列中等待;非公平锁则需要去抢夺

// 公平锁
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;
}

// 非公平锁
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {	// 由 0 变 1 
            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;
}

由于 FairSync 和 NonfairSync 都是要调用 acquire 方法,置 1 表示有线程抢到了锁

lock()方法

以 NonfairSync 为例,AQS acquire 主要的三条流程

// ReentrantLock 中的 lock 方法
public void lock() {
    sync.lock();
}
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;
    // Sync中的抽象方法
    abstract void lock();
    ...
}
// FairSync 和 NonfairSync 两个方法都继承了 Sync方法的 lock 操作,下面的代码是 NonfairSync 内部类的 lock 方法实现
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
// 进入acquire方法
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))	// Node.EXCLUSIVE 独占的 node 结点
        selfInterrupt();
}

1、调用tryAcquire

// 由AQS中的tryAcquire方法
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

// 在非公平的NonfairSync中的实现
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 从希望值的0 修改为1
        if (compareAndSetState(0, acquires)) {
            // 第一个线程抢占到了
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 如果是被占用的线程就进入,不是的话返回false
    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;
}

2、调用addWaiter方法:入队

private Node addWaiter(Node mode) {
    // 封装一个当前线程,mode 是 排他模式
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {		// 等待队列中有线程在等待了
        node.prev = pred;		// 把当前队列中的尾指针指向的结点赋值给新进线程的前驱结点
        if (compareAndSetTail(pred, node)) {	// 将新进的node设置为尾指针
            pred.next = node;		// 把之前尾结点的后继结点设置为新进结点node
            return node;
        }
    }
    // 进入结点
    enq(node);
    return node;
}

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))	// 入队的结点不是传入的结点(虚拟节点&哨兵节点,作用就是占位),new Node(){Thread = null;waitStatus = 0;},而是一个新的结点,设置为头/尾结点
                tail = head;
        } else {
            node.prev = t;	// 把 t(也就是伪指针,指的是新的node)赋值给 node (传入的结点)的前指针
            if (compareAndSetTail(t, node)) {	// 把node设置为尾指针
                t.next = node;
                return t;
            }
        }
    }
}

在这里插入图片描述
在这里插入图片描述

3、调用 acquireQueued 方法

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        // 正常的入队出队
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();	// B结点的前驱结点是虚拟结点,也是头结点
            if (p == head && tryAcquire(arg)) {	// 如果当前还有线程占用,就往下走,如果没有进入代码块。tryAcquire(arg) 可能来夺锁
                setHead(node);	// 将当前结点设置为头结点
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&	// B结点的前驱结点也就是虚拟结点,node就是B结点
                parkAndCheckInterrupt())	
                interrupted = true;
        }
    } finally {
        // 意外中断,取消排队
        if (failed)
            cancelAcquire(node);
    }
}
private void setHead(Node node) {
    head = node;	// 将当前结点B设置为头结点
    node.thread = null;		// 将头结点的thread置为null,没有前驱指针
    node.prev = null;
}

// shouldParkAfterFailedAcquire(p, node) 的源码,前驱结点为SIGNAL返回true
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;	// 获取虚拟结点在队列中的状态,有0(默认值),1,-1,-2,-3等状态
    if (ws == Node.SIGNAL)	// 如果当前状态为-1,就是线程准备好了,返回true
        return true;
    if (ws > 0) {	// 如果线程获取锁的活动取消了
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);	// 前置节点(头结点)设置 waitStatus 为 Node.SIGNAL(-1)
    }
    return false;
}
// parkAndCheckInterrupt() 方法,也就是this就是B(NonfairSync对象)转着圈,等待这争取锁
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

在这里插入图片描述

unlock() 方法

unlock --> release --> tryRelease/unparkSuccessor

// ReentrantLock 中的 unlock 方法
public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {	// 释放锁
        Node h = head;	// h 为虚拟结点
        if (h != null && h.waitStatus != 0)	// 等待队列中有线程再等待同时 waitStatus = 0
            unparkSuccessor(h);
        return true;
    }
    return false;
}

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;		// 1 - 1 = 0 = c
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);	// 设置 state = 0;说明已经解锁
    return free;	// 返回 true
}

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;	// 头结点为-1
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);	// 设置头结点(虚拟结点)status为0

    Node s = node.next;	// 来到真实的结点B
    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);
}
// 对队列中的等待进行唤醒
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

在这里插入图片描述

cancelAcquire()

cancelAcquire 方法取消流程

private void cancelAcquire(Node node) {		// 比如5号节点
    if (node == null)
        return;

    node.thread = null;		// 线程为null

    Node pred = node.prev;	//	5号节点的前驱结点就是4号结点
    while (pred.waitStatus > 0)	// 如果4号结点也取消,指针继续前移
        node.prev = pred = pred.prev;

    Node predNext = pred.next;	

    node.waitStatus = Node.CANCELLED;	// 5号结点的waitStatus设置为1

    if (node == tail && compareAndSetTail(node, pred)) {	// 5号结点是尾结点并且把尾指针指向4号结点
        compareAndSetNext(pred, predNext, null);	// 把4号结点的后继结点设为null
    } else {		// 删除的不是尾结点
        int ws;		
        if (pred != head &&		// 前驱不是头结点
            ((ws = pred.waitStatus) == Node.SIGNAL ||	// 如果前驱结点的 waitStatus 是 -1 (随时待命)
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&	// 线程没有被取消,把前驱结点的线程的waitStatus置为-1
            pred.thread != null) {	// 前驱结点的thread不为空
            Node next = node.next;	// 把5号结点的后继结点给next
            if (next != null && next.waitStatus <= 0)	// 后继结点为不空
                compareAndSetNext(pred, predNext, next);
        } else {
            unparkSuccessor(node);	// 
        }

        node.next = node; // help GC
    }
}

// 
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);	// 设为默认值

    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);	// 出去抢夺锁
}

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

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

相关文章

【企业信息化】第3集 世界排名第一的免费开源ERP: Odoo 16 POS终端管理系统

文章目录 前言一、概览二、硬件三、使用功能 前言 世界排名第一的免费开源ERP: Odoo 16 POS终端管理系统。几分钟内完成设置&#xff0c;几秒内完成销售。 一、概览 Odoo POS 基于智能界面&#xff0c;任何零售公司均可毫不费力地使用 因为其极具灵活性&#xff0c;您可配置 …

普通的项目非分布式项目中的技术点思考(学习随记)

学习路线 在学习Java的路程中&#xff0c;最开始学习JavaSe&#xff0c;在Java基础学完后&#xff0c;开始接触JavaWeb&#xff0c;开始接触框架&#xff0c;Spring框架&#xff0c;SpringBoot框架、数据库框架、在学习一下中间件&#xff0c;就可以完成工作中crud的基础操作&…

聚观早报|谷歌:全新大模型赋能「全家桶」;阿里巴巴取消CTO职位

今日要闻&#xff1a;谷歌 I/O&#xff1a;全新大模型赋能「全家桶」&#xff1b;阿里巴巴取消CTO职位&#xff1b;马斯克打造「美国微信」&#xff1b;奔驰将召回部分进口CLA汽车&#xff1b;奔驰将召回部分进口CLA汽车 谷歌 I/O&#xff1a;全新大模型赋能「全家桶」 北京时…

大模型也内卷,Vicuna训练及推理指南,效果碾压斯坦福羊驼

2023开年以来&#xff0c;大模型进入疯狂内卷状态&#xff0c;大模型的发布都要以“天”为单位进行迭代。 之前&#xff0c;尝试了从0到1复现斯坦福羊驼&#xff08;Stanford Alpaca 7B&#xff09; &#xff0c;下面我们来尝试从0到1复现Vicuna训练及推理。 Vicuna简介 继斯坦…

AOP深度学习

代理模式 静态代理&#xff1a;静态代理确实实现了解耦&#xff0c;但是由于代码都写死了&#xff0c;完全不具备任何的灵活性。就拿日志功能来说&#xff0c;将来其他地方也需要附加日志&#xff0c;那还得再声明更多个静态代理类&#xff0c;那就产生了大量重复的代码&#…

wps js宏编辑器案例2-单元格读写-随机选人

本案例讲述某企业的一个真实案例&#xff0c;该企业每周二早上有安全宣贯会议&#xff0c;差不多10来分钟左右&#xff0c;每次安全会上人事部门都会点名&#xff0c;那么问题来了&#xff0c;点名的名单哪儿来&#xff1f;为此&#xff0c;编写了一个简单js宏应用&#xff0c;…

易基因:DNA甲基化和转录组分析揭示野生草莓干旱胁迫分子调控机制|植物抗逆

大家好&#xff0c;这里专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 干旱胁迫是对农业生产产生不利影响的关键环境因素。为此&#xff0c;植物发展出各种响应机制&#xff08;干旱逃逸、避免、耐受和回复&#xff09;&#xff0c;以通过进化增强抗旱性&#…

2022年下半年软件设计师下午试题

试题四&#xff08;共15分&#xff09; 排序是将一组无序的数据元素调整为非递减顺序的数据序列的过程&#xff0c;堆排序是一种常用的排序算法。用顺序存储结构存储堆中元素。非递减堆排序的步骤是&#xff1a; (1)将含n个元素的待排序数列构造成一个初始大顶堆&#xff0c;…

种棉12载的他,为何最终选择千耘导航?

边休息边种地&#xff0c;每天还能提升近四十亩作业量&#xff0c;是怎么做到的&#xff1f; 种地十二三年&#xff0c;为何最终选择了千耘农机导航&#xff1f; 千耘导航使用前后的工作状态究竟相差了多少&#xff1f; 让我们走进新疆阿克苏&#xff0c;听一听任师傅的“种…

【WebGIS实例】(8)MapboxGL绘制闪烁的点

官网示例&#xff1a; Add an animated icon to the map | Mapbox GL JS 实现 示例数据 const sampleData {"type": "FeatureCollection","features": [{"type": "Feature","properties": {},"geometry&q…

5G干扰排查优化方案介绍!

干扰成因 干扰源的发射信号&#xff08;阻塞信号、加性噪声信号&#xff09;从天线口被放大发射出来后&#xff0c;经过了空间损耗L&#xff0c;最后进入被干扰接收机。如果空间隔离不够的话&#xff0c;进入被干扰接收机的干扰信号强度够大&#xff0c;将会使接收机信噪比恶化…

【教程】安装VSCode-Server

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 前景提要 jupyter虽然好用&#xff0c;但他只能运行ipynb文件&#xff0c;对于py文件还是只能通过命令行来运行&#xff0c;不是很方便。 因此&#xff0c;通过安装vscode来运行py文件。而vscode-server支持像jup…

Centos-7安装步骤教程

提示&#xff1a; 鼠标移动到虚拟机内部单击或者按下Ctrl G&#xff0c;鼠标即可移入到虚拟机中&#xff0c;按下Ctrl Alt&#xff0c;鼠标即可移出虚拟机 目录 一、虚拟机的创建 1、创建新的虚拟机 2、选择典型&#xff0c;也可以自定义 3、安装程序光盘映像文件&#x…

618大促即将来临,速卖通、Lazada等平台如何快速提高排名和转化率?

速卖通每年三大促&#xff0c;328、618、双11。618马上就要来临&#xff0c;卖家朋友们都准备好了吗&#xff1f;今天陈哥就和大家聊聊怎么快速提高产品转化率。转化率是卖家在分析复盘时非常关键的因素&#xff0c;转化率的高低直接影响着卖家目前的关键词listing或者商品描述…

4.5 队列实现及其应用(上)

目录 顺序队列 创建空队列&#xff1a; 判断队列空&#xff1a; 入队&#xff1a; 队列 队列是限制在两端进行插入操作和删除操作的线性表 允许进行存入操作的一端称为“队尾” 允许进行删除操作的一端称为“队头” 当线性表中没有元素时&#xff0c;称为“空队” 特点 &am…

监控室值班人员脱岗识别算法 opencv

监控室值班人员脱岗识别算法模型通过pythonopencv网络深度学校模型技术&#xff0c;监控室值班人员脱岗识别算法模型实现企业总监控室值班人员脱岗、睡岗、玩手机等场景的AI识别&#xff0c;不需人为干预全天候自动识别。OpenCV的全称是Open Source Computer Vision Library&am…

git码云的使用-创建项目仓库-ssh协议配置步骤

目录 1、创建仓库 1.1 只填入仓库名即可-提交 1.2 本地项目上传到远程仓库 2、提交仓库 2.1 选择HTTPS协议 2.2 选择ssh协议 3、ssh协议配置步骤 3.1 打开 Git Bash 3.2 生成公钥&#xff1a;$ cd ~/.ssh &#xff08;可忽略&#xff09; 3.3 生成密钥 3.4 添加公钥…

【零基础QQ机器人开发三】程序上云篇

前言&#xff1a;本文为大家带来QQ机器人程序上云的教程&#xff0c;环境搭建请参考下面链接 【0基础QQ机器人开发】基于go-cqhttp的QQ机器人开发教程,仅供自学 【零基础QQ机器人开发二】服务器篇 文章目录 程序Logger类StatuStore类MultiFunc类QQBot类main.py 前言&#xff1a…

AP3266 DC-DC大功率同步降压恒流芯片 过EMC三级 摩托电动汽车灯IC

1&#xff0c;产品描述 AP3266 是高效率、外围简单、内置功率管的同步降压恒流芯片&#xff0c;适用于4-40V输入的降压LED恒流驱动芯片。输出功率可达 40W&#xff0c;电流3.6A。AP3266 可通过调节 OVP 端口的分压电阻&#xff0c;设定输出空载电压 保护&#xff0c;避免高压 空…

DenseNet与ResNet

ResNet&#xff08;深度残差网络&#xff09; 深度残差网络 DenseNet 采用密集连接机制&#xff0c;即互相连接所有的层&#xff0c;每个层都会与前面所有层在channel维度上连接在一起&#xff0c;实现特征重用&#xff0c;作为下一层的输入。 这样不但缓解了梯度消失的现象…