JUC锁:核心类 AQS(AbstractQueuedSynchronizer)

JUC锁:核心类 AQS(AbstractQueuedSynchronizer)

AQS是一个用于实现依赖FIFO等待队列的阻塞所和相关同步器的基类。有两个模式:exclusive(独占)share(共享) 子类可以实现其中的一种也可以两种都支持

AQS 使用一个int成员变量表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。

成员属性

//等待队列的头部,延迟初始化。除初始化外,仅通过 setHead 方法进行修改。注意:如果 head 存在,则保证其 waitStatus 不是CANCELLED
private transient volatile Node head;
//等待队列的尾部,延迟初始化。仅通过方法 enq 修改以添加新的等待节点
private transient volatile Node tail;
//同步状态,共享变量,使用volatile修饰保证线程可见性。使用CAS对该同步状态进行原子操作
private volatile int state;

state 操作方法

//返回当前同步状态的值
protected final int getState() {
    return state;
}

//设置同步状态值
protected final void setState(int newState) {
    state = newState;
}

//使用CAS操作修改同步状态。如果当前状态值等于预期值,则原子的将同步状态设置为给定的更新值
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

静态内部类

等待队列Node 类

作为AQS类的底层数据结构,CLH队列是一个虚拟的双向队列(虚拟双向队列及不存在队列实例,仅存在节点之间的关联关系)。

如果要加入CLH锁,可以原子性的将其拼接到尾部,如果要出列设置头head字段。CLH队列需要创建一个虚拟的头结点开始(因为如果不存在争用,那么在构件时创建则可能造成浪费。在第一次出现争用的时候构造节点并且设置头指针和尾指针)

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;
        //表示当前节点在等待condition,也就是在condition队列中
        static final int CONDITION = -2;
        //表示当前场景下的后续acquireShared 能够得以执行
        static final int PROPAGATE = -3;
        // 0 表示普通节点,使用 CAS进行修改
    
        //节点状态
        volatile int waitStatus;

        //前驱结点
        volatile Node prev;

        //后继节点
        volatile Node next;

        //节点所对应的线程对象
        volatile Thread thread;

       //下一个等待者
        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() {    // Used to establish initial head or SHARED marker
        }
       //创建一个等待队列 构造方法
        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }
     // 用于创建条件队列 构造方法
        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

每个被阻塞的线程都会被分装成一个Node节点,放入队列。每个节点包含了一个Thread类型的引用,并且每个节点都存在一个状态:

CANCELLED,值为1 ,表示当前线程被取消

SIGNAL 值为-1 表示后继节点在等待当前线程唤醒。后继节点入队时,会将前置节点的状态更新为SINGNAL

CONDITION 值为-2 ,表示当前节点在等待condition,即在condition queue 中

PROPAGATE 值为 -3 ,表示当前场景下的后续acquireShared 能够得以执行

值为0, 表示当前节点在 sync queue中,等待获取锁

获取资源方法 acquire

//由 子类进行 重写该方法 进行实现
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

//final修饰 不允许子类擅自修改 以独占的方式进行获取锁,忽略中断
    public final void acquire(int arg) {
        if (!tryAcquire(arg) && //尝试获取锁
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

①先尝试获取锁,如果尝试获取锁失败,先向 阻塞队列中添加当前线程的Node节点

private Node addWaiter(Node mode) {
        //创建当前线程的node节点
        Node node = new Node(Thread.currentThread(), mode);
        // 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)) { //通过CAS操作将当前节点作为尾节点
                pred.next = node; //前置节点指向新的尾节点
                return node;
            }
        }
        // 如果尾节点为空或者 尝试cas操作修改尾节点失败
        enq(node);
        return node;
    }
  • addWaiter 向 阻塞队列队尾添加元素:
    • 如果先快速尝试向队列尾部添加元素,如果失败了则调用enq方法 以自旋 + CAS 的方式 尝试进行将当前线程的Node节点添加到列表尾部

②尝试获取锁失败后,在阻塞队列中 节点不断获取资源

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 //将之前的头结点置为null
                failed = false;
                return interrupted;
            }
            //这个位置未使用CAS方式不断的尝试获取锁,而是将未排队到的节点进行挂起排队。减少了性能开销
            // 如果返回true 说明前置节点也在尝试获取锁,则进行挂起操作
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
  • 首先判断当前节点的前置节点,如果是前置节点,则当前节点可以尝试去获取资源
  • 否则调用 shouldParkAfterFailedAcquire 检查和更新未能获取的节点的状态。如果为CANCLE状态的节点进行删除。如果为SIGNAL状态返回true
  • 如果前置节点状态为SIGNAL 则将当前节点桶park方法进行挂起

释放资源方法 release

public final boolean release(int arg) {
    if (tryRelease(arg)) { //尝试获得锁
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h); //通过cas操作修改 head节点状态,并唤醒头结点的后继节点
        return true;
    }
    return false;
}

通过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) //CAS 操作将状态改为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;
        //从尾节点开始搜索,找到waitStatus<=0 并且最靠前的非head的节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        //进行唤醒
        LockSupport.unpark(s.thread);
}

为什么next 节点为null或状态>0的 则从尾节点开始进行遍历, 将有效节点进行前移

如果next节点不为空 调用Unpark 进行唤醒。进行竞争资源

总结

AQS(AbstractQueuedSynchrinizer) ,是一个构建阻塞式锁和相关同步器工具和框架

AQS的核心思想是什么,他是怎么实现的?

  • 核心思想

如果被请求的共享资源空闲,则当前请求的资源的线程设置为有效线程,并且将共享资源设置为锁定状态。

如果被请求的资源被占用,那么就通过CLH 队列锁,将阻塞等待线程及被唤醒的线程进行添加到队列中。

通过使用一个被volatile 修饰的int 变量表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。

使用CAS对该同步状态进行原子操作修改。

AQS使用了什么设计模式

AQS底层使用了模版方法,自定义同步器在实现时只需要实现共享资源state的获取和释放的方式即可。具体如何将阻塞线程添加到队列及如何唤醒AQS已经实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值