当前位置:网站首页>【无标题】

【无标题】

2022-07-01 08:15:00 今天学不学?

AQS

AQS在java并发中的地位,相当于JVM在java中的地位,他就是抽象阻塞队列同步器,是实现同步器和锁的基石,就是base父类

抽象类:相当于base父类,可以是很多类的模板框架,组件,在抽象类的子类有很多情况

队列:用于锁的争抢,没抢到资源的到阻塞队列排队

概述:用于实现锁和其他同步器组件的公共基础部分的抽象实现,是重量级的基础框架以及JUC体系的基石,就是一个抽象FIFO队列来完成资源获取线程的阻塞队列和一个int变量的状态来表示当前资源是否被占用

Java中的大部分同步类(Lock、Semaphore、ReentrantLock等)都是基于AbstractQueuedSynchronizer(简称为AQS)实现的。AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。

在AQS中的锁类型有两种:分别是**「Exclusive(独占锁)**「和」**Share(共享锁)」**。

「独占锁」就是「每次都只有一个线程运行」,例如ReentrantLock

「共享锁」就是「同时可以多个线程运行」,如Semaphore、CountDownLatch、ReentrantReadWriteLock。名称为AbstractQueueSynchronized,是阻塞式锁和相关的同步器工具的框架

概括:整体上就是一个抽象的FIFO队列来完成资源获取线程的排队任务,并通过一个int类变量表示持有锁的状态

在这里插入图片描述

AQS框架

AQS框架图

在这里插入图片描述

AQS内部体系框架

在这里插入图片描述

数据结构

AQS最基本的数据结构为Node节点

在这里插入图片描述

属性解释:

waitStatus:在队列中节点的状态

thread:当前节点的线程

prev:前驱节点

predecessor:返回前驱节点,没有的话抛出npe

nextWaiter:指向下一个处于CONDITION节点

next:后继指针

waitStatus属性值种类:

枚举含义
0当一个Node被初始化的时候的默认值
CANCELLED为1,表示线程获取锁的请求已经取消了
CONDITION为-2,表示节点在等待队列中,节点线程等待唤醒
PROPAGATE为-3,当前线程处在SHARED情况下,该字段才会使用
SIGNAL为-1,表示线程已经准备好了,就等资源释放了

源码分析

ReentrantLock中的非公平锁来进行源码分析

  1. 从lock方法进行入手

    ReentrantLock实现了Lock接口,里面包含了一个sync同步器,该同步器继承了AQS类,底层lock调用了sync同步器的lock锁

public static void main(String[] args) {
    
    ReentrantLock lock = new ReentrantLock();
    lock.lock();
    try{
    
        //同步代码块{}
    }finally {
    
        lock.unlock();
    }
   
}

2.sync同步器的lock方法

  • 首先CAS进行判断,能否当前线程先抢到锁,如果成功,该线程直接获取资源,否则进行acquire方法尝试获取资源
final void lock() {
    
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

3.acquire方法

该方法主要有三大体系

  • tryAcquire:尝试获取锁,如果再次没有获取到锁,返回false,进行addWaite(Node.EXCLUSIVE)r方法的执行
  • addWaiter(Node.EXCLUSIVE):如果没有获取成功,将线程加入到队列中
  • 最后调用acquireQueued()方法:通过 “死循环”的方式获取同步状态
public final void acquire(int arg) {
    
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

加入队列的时机

当执行acquire(1)时,会通过tryAcquire获取锁,在这种情况下,如果获取锁失败,就会调用addWaiter加入到等待队列中去

final boolean nonfairTryAcquire(int acquires) {
    
    final Thread current = Thread.currentThread();
    int c = getState();//先获取资源的状态
    if (c == 0) {
    //如果为0,表示该资源没有被占用,可以进行获取
        if (compareAndSetState(0, acquires)) {
    
            setExclusiveOwnerThread(current);//获取资源,返回true
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
    //如果获取的资源线程为当前资源线程,返回true
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

如何加入队列

获取锁失败后,执行addwaiter加入等待队列

private Node addWaiter(Node mode) {
    
    Node node = new Node(Thread.currentThread(), mode);//新建一个node节点
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;//取出当前尾结点
    if (pred != null) {
    //如果尾节点不为空 说明队列已存在阻塞线程
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
    
            pred.next = node;
            return node;
        }
    }
    enq(node);//如果尾节点为空,表示阻塞队列中还没node节点,进行入队操作
    return node;
}

入队操作

private Node enq(final Node node) {
    
    for (;;) {
    
        Node t = tail;
        if (t == null) {
     //进行队列的初始化
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
    
            node.prev = t;
            if (compareAndSetTail(t, node)) {
    
                t.next = node;
                return t;
            }
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZUKGENuu-1656424435234)(C:\Users\小刘同学\Desktop\入队操作.png)]

等待队列中线程出队时机

前驱是头结点,就获取到了同步状态。

head节点表示获取锁成功的节点,当头结点在释放同步状态时,会唤醒后继节点,如果后继节点获得锁成功,会把自己设置为头结点,节点的变化过程如下
移除节点的变化
这个过程也是涉及到两个变化

  • 修改head节点指向下一个获得锁的节点
  • 新的获得锁的节点,将prev的指针指向null

这里有一个小的变化,就是设置head节点不需要用CAS,原因是设置head节点是由获得锁的线程来完成的,而同步锁只能由一个线程获得,所以不需要CAS保证,只需要把head节点设置为原首节点的后继节点,并且断开原head节点的next引用即可

img
原网站

版权声明
本文为[今天学不学?]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_48304611/article/details/125510597