【多线程】AQS(上)

发布于:2024-10-13 ⋅ 阅读:(25) ⋅ 点赞:(0)

前置知识

  • 公平锁与非公平锁
    • 公平锁:锁被释放之后,先申请的线程会先得到锁.公平锁的性能相比于非公平锁性能较差,因为公平锁为了保证时间上的绝对顺序,上下文切换更为频繁.
    • 非公平锁:锁被释放之后,后申请的线程也可能会先得到锁 ,是随机或者按照其他优先级排序的.性能更好,但可能会导致某些线程永远无法获取到锁.
  • 可重入锁
    • 可重入锁也叫做递归锁,指的是同一个线程可以再次获取自己的内部锁,比如一个线程获取到对象锁,此时这个对象锁还没有释放,当其想再次获取到这个对象锁的时候还是可以再获取的,如果不可重入,可能会导致死锁的情况发生.
  • 自旋思想
    • 当线程请求锁时,如果锁已经被其他线程持有,那么该线程就会不断重试获取锁,而不是挂起等待,这个不断尝试获取锁的行为称之为自旋.
  • LockSupport
    • 作用:一个工具类,用于线程的阻塞和唤醒操作,类似于wait()notify() 方法,但是更加灵活和可控
    • 提供了park()unpark()两个静态方法,用于线程阻塞和唤醒操作
    • 优点在于可以在任意时刻阻塞和唤醒线程而不需要实现获取到锁或者监视器对象
  • 数据结构与双向链表
    • 双向链表(Double Linked List)是一种常见的数据结构,它是由一系列节点(Node)组成的,每个节点包含三个部分**:数据域,前驱指针,后继指针.其中,数据域存储节点的数据,前驱节点指向前一个节点,后继节点指向后一个节点.通过这种方式,双向链表可以实现双向遍历和插入,删除的操作.
  • 设计模式----模版设计模式
    • 模版设计模式是一种行为型设计模式,定义了一种算法的框架,并将某些步骤延迟到子类中,这种设计模式的主要目的是允许子类在不改变算法结构的情况下重新第一算法中的某些步骤
    • 优点:能够提高代码的复用性和可维护性

AQS的基本概念

基本概念

  • AQS全称:AbstructQueueSynchronizer(抽象的队列同步器)
  • AQS作用:为实现阻塞锁和相关同步器提供了一个框架,解决的是"锁分配"的问题
  • AQS核心思想:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中
  • AQS的设计模式:模板方法模式
  • AQS源码中的基本介绍:在这里插入图片描述
  • CLH队列:
    在这里插入图片描述
    CLH:是一个单向链表,AQS队列中是CLH变体的虚拟双向队列FIFO

AQS的基本架构

在这里插入图片描述

public abstract class AbstractQueuedSynchronizer
  extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
-----------------------将线程包装成一个个Node加入等待队列----------------------------------------------------------
    static final class Node {
                .....//
        }  
---------------------------------同步器---------------------------------------------------------        
    private final Sync sync = new Sync();   
  -------------------------------CLH队列----------------------------------------------
    // 以下为双向链表的首尾结点,代表入口等待队列,所以FIFO是一个双向链表
    private transient volatile Node head;//头结点
    private transient volatile Node tail;//为节点
    
---------------共享变量 state,核心属性,关于它的枚举值下文分析---------------------------
    private volatile int state;//资源锁的状态
    
    // 对state进行CAS操作,确保线性安全
    protected final boolean compareAndSetState(int expect, int update) {   
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
  //......  
 }

AQS核心实现:

  • Node:将线程封装成Node节点,再由一个FIFO队列进行维护
  • volatile修饰的int类型的整型变量"state" : 标记资源的占用情况
  • CLH队列变体(FIFO) :完成线程获取资源的排队工作
    对象同步器sync:Lock接口的实现类中,均会有聚合了一个对象同步器的子类(sync)完成线程访问控制

AQS中的节点(Node)

static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
//由于超时或中断,此节点被取消
static final int CANCELLED =  1;
//此节点后面的节点被阻塞(park),避免竞争,资源浪费,此节点释放后通知后面的节点
static final int SIGNAL    = -1;
//表示这个Node在条件队列中,因为等待某个条件而被阻塞
static final int CONDITION = -2;
//使用在共享模式头Node有可能处于这种状态, 表示锁的下一次获取可以无条件传播
static final int PROPAGATE = -3;
volatile int waitStatus;
//前驱节点
volatile Node prev;
//后继节点
volatile Node next;
//线程对象
volatile Thread thread;
Node nextWaiter;

节点中的几个重点属性:

  • static final Node SHARED:共享锁
  • static final Node EXCLUSIVE = null:独占锁
  • CANCELLED:表示线程是否被取消,1表示该线程被取消
  • SIGNAL:后续线程需要唤醒-1表示此节点后面的节点被阻塞(park),避免竞争,资源浪费,此节点释放后通知后面的节点
  • CONDITION :等待condition唤醒,-2表示这个Node在条件队列中,因为等待某个条件而被阻塞
  • PROPAGATE :使用在共享模式Node有可能处于这种状态, 表示锁的下一次获取可以无条件传播
  • waitStatus:表示当前的Node在队列中的等待状态
    • 0:当一个Node被初始化的时候的默认值
    • CANCELLED:为1,表示线程获取锁的请求已经取消
    • CONDITION:为-2,表示节点在等待队列中,节点线程等待唤醒
    • PROPAGATE:为-3,表示当前线程处在SHARED情况下,该字段才会使用
    • SIGNAL:为-1,表示线程已经准备好了,就等资源释放

State:Volatile修饰的Int类型的变量

private volatile int state;//资源锁的状态

这里为什么要使用volatile来修饰?
保证内存可见性,即共享资源的状态发生改变的时候,其他线程能立刻感知到.

AQS类中,使用到了CAS思想,保证了对state操作的原子性

protected final boolean compareAndSetState(int expect, int update) {
        return U.compareAndSetInt(this, STATE, expect, update);
    }

CLH队列

    private transient volatile Node head;//头结点
    private transient volatile Node tail;//尾节点

AQS类中维护了两个节点,并且有关头/尾节点的几个操作:
尝试加入一个新的Node节点放到尾节点处,CAS保证原子性

private boolean casTail(Node c, Node v) {
        return U.compareAndSetReference(this, TAIL, c, v);
    }

尝试向CAS提供一个新的头部虚拟节点:

private void tryInitializeHead() {
        Node h = new ExclusiveNode();
        if (U.compareAndSetReference(this, HEAD, null, h))
            tail = h;
    }

sync

我们知道,AQS是为了解决锁分配给哪个线程问题的,所以,Lock接口的实现类中,均会有聚合了一个对象同步器的子类(sync)完成线程访问控制
ReentrantLock:
在这里插入图片描述

进一步理解锁和同步器之间的关系:

  • 锁:面向的是锁的使用者,定义了程序员和锁交互的使用层API,隐藏了实现细节,直接调用即可
  • 同步器:面向锁的实现者,DoungLee提出了统一规范并简化了锁的实现,将锁的实现抽象出来,屏蔽了同步状态管理,同步队列的管理与维护,阻塞线程排队和通知,唤醒机制等,是一切锁和同步组件实现的公共基础部分

AQS原理总结

AQS是将暂时无法请求共享资源的线程封装成一个CLH队列 (虚拟的双向队列)的一个节点来 实现锁的分配.
根据volatile修饰的state共享变量,线程通过CAS去改变状态.如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现,即将暂时获取不到锁的线程加入到队列中,等待被唤醒
图像解释:
在这里插入图片描述

下篇我们会通过源码进行详细的分析