首页 > 其他分享 >AQS

AQS

时间:2022-11-07 11:23:32浏览次数:42  
标签:Node AQS 队列 final state 线程 节点

1、Lock规范用法

Lock lock = new ReentrantLock();
//加锁
lock.lock();
try{
    doSomething();
}finally {
    //解锁
    lock.unlock();
}

2、AQS

AQS:Abstract Queued Sychronized

可以看到整个结构的关键就是node

//此处是 Node 的部分属性
static final class Node {
 
 //排他锁标识
 static final Node EXCLUSIVE = null;

 //如果带有这个标识,证明是失效了
 static final int CANCELLED =  1;
 
 //具有这个标识,说明后继节点需要被唤醒
 static final int SIGNAL = -1;

 //Node对象存储标识的地方
 volatile int waitStatus;

 //指向上一个节点
 volatile Node prev;

 //指向下一个节点
 volatile Node next;
 
 //当前Node绑定的线程
 volatile Thread thread;
 
 //返回前驱节点即上一个节点,如果前驱节点为空,抛出异常
 final Node predecessor() throws NullPointerException {
  Node p = prev;
  if (p == null)
   throw new NullPointerException();
  else
   return p;
 }
}

node的状态,node的状态来控制线程队列的唤醒逻辑

状态值
1(CANCELLED) 当前节点取消获取锁。等待超时或者被中断,会变更为该状态,进入该状态节点状态不再进行变化
-1(SIGNAL) 后面节点等待当前节点唤醒
-2(CONDITION) 当前线程阻塞在Condition,如果其他线程调用了Condition的signal方法,这个节点将从等待队列转移到同步队列队尾,等待获取同步锁
-3(PROPAGATE) 共享模式,前置节点唤醒后面节点后,唤醒操作无条件传播下去
0(中间状态) 当前节点后面的节点已经唤醒,但是当前节点线程还是没有执行下去

3、加锁

前提:现在有2个线程,线程1,线程2进入加锁逻辑。
加锁代码:

        //公平锁    	
		final void lock() {
            acquire(1);
        }

		//非公平锁
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

非公平锁:CAS设置AQS的state属性,如果设置成功,设置AQS的ownerThread为当前线程,否则进入acquire方法进行尝试。线程1 cas 设置state = 1,设置成功,设置ownerThread为线程1,返回。线程2进入lock,cas设置state = 1,设置失败,失败后进入acquire。

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

可以被描述为:

  • tryAcquire:当前线程尝试获取,尝试成功,返回,否则进入下一步
  • addWaiter:将当前线程包装为AQS的node
  • acquireQueued:将当前节点加入争抢线程的逻辑中
  • selfInterrupt:线程自我中断

3.1、tryAcquire

查看非公平锁的tryAcquire实现

  final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
        	//获取AQS的状态
            int c = getState();
        	//当前锁可以重新获取
            if (c == 0) {
                //cas设置state
                if (compareAndSetState(0, acquires)) {
                    //case设置state成功,设置ownerThread为当前线程
                    setExclusiveOwnerThread(current);
                    //获取成功
                    return true;
                }
            }
                //如果当前线程是onwerThread
            else if (current == getExclusiveOwnerThread()) {
                //设置state,重入锁的实现
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //设置state的新值
                setState(nextc);
                //获取成功
                return true;
            }
        	//获取锁失败
            return false;
}

3.2、addWaiter

如果队列中没有元素

如果队列中有元素,aqs设置节点为队尾成功:

aqs设置队尾失败
死循环将节点追加到队尾

3.3、acquireQueued

队列中的线程不断尝试获取锁

  • 如果节点的前置节点是头节点,使用tryAcquire尝试获取
  • 获取成功,设置当前节点为头节点
  • 获取失败,调用shouldParkAfterFailedAcquire
    shouldParkAfterFailedAcquire实现流程图,这个方法是用来排出队列中一些不正常状态的节点的。

    parkAndCheckInterrupt,将本线程暂停。等待unpark方法执行的时候,如果该节点是头节点的下一个节点,那么线程继续执行,继续acquireQueued的无限循环,尝试获取锁。

4、解锁

 public final boolean release(int arg) {
     
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease

 protected final boolean tryRelease(int releases) {
            //state减1
            int c = getState() - releases;
        	//当前线程!=ownerThread,抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
        	//如果c==0代表可以解锁,清理ownerThrad
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
        	//设置state的值的新的值
            setState(c);
            return free;
        }

如果state设置完成,开始unpark队列中的节点

 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)
            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;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

5、总结

state来标记是否可以加锁,用队列中节点封装争抢执行权的线程,用节点的waitStatus来处理节点的生命周期。
加锁有几个关键步骤:tryAcquire方法,用来判断锁是被获取,获取的线程是自己,如果不是失败。
addWaiter将节点封装为队列中的node,追加队列的队尾,如果队列中没有元素,创建一个虚拟节点作为头节点。
acquireQueued:新加入队列的节点去竞争锁,如果节点的前置节点是头节点,使用tryAcquire去竞争,如果失败,将队列中的节点重新组织,清楚失效节点,然后将当前线程阻塞住。
解锁:修改state状态以及ownerThread,寻找第一个可以被释放的节点,释放节点,acquireQueued方法无限循环继续,继续上述流程。

标签:Node,AQS,队列,final,state,线程,节点
From: https://www.cnblogs.com/zhangchiblog/p/16865356.html

相关文章

  • 详解AQS中的condition源码原理
    摘要:condition用于显式的等待通知,等待过程可以挂起并释放锁,唤醒后重新拿到锁。本文分享自华为云社区《AQS中的condition源码原理详细分析》,作者:breakDawn。condition的用......
  • Java多线程(6):锁与AQS(下)
    您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~ 之前说过,AQS(抽象队列同步器)是Java锁机制的底层实现。既然它这么优秀,是骡子是马,就拉出来溜溜吧。首先用重入锁来实现简......
  • Java多线程(6):锁与AQS(中)
    您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~ Java中的AQS(AbstractQueuedSynchronizer,抽象队列同步器)是用来实现锁及其他同步功能组件的Java底层技术基础,java.util.co......
  • Java多线程(6):锁与AQS(上)
    您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~ 在Java面试中,有一类高频问题会经常问到(火箭式问题):Java有几种锁?都是干嘛的?我想对于面试经验较为丰富的人,这个问题极有可......
  • AQS相关笔记
    电脑修了快20填了,还没修好,我服了。。。也没有好记笔记和学习的地方,所以干脆在这里记笔记好了。AQSAQS具备特性:1.阻塞等待队列2.共享/独占3.公平/非公平4.可重入......
  • JUC中的AQS底层详细超详解
    摘要:当你使用java实现一个线程同步的对象时,一定会包含一个问题:你该如何保证多个线程访问该对象时,正确地进行阻塞等待,正确地被唤醒?本文分享自华为云社区《JUC中的AQS底层详......
  • 手写一个AQS实现
    1.背景2.代码-基础准备2.1.Node节点对象创建在MyReentrantLock对象内建立一个Node节点对象,后面作为双向链表的节点;publicclassMyReentrantLock{/***......
  • AQS专题
    1.背景2.预备知识2.1.park、unpark、interrupt、isInterrupted、interrupted方法的理解一:park、unpark1.park、unpark它不是Thread中的方法,而是LockSupport.park(),Lo......
  • AQS源码深度解析之cancelAcquire方法解读
    1.背景2.源码解读调用该方法的地方 方法源码解读/***取消获取资源(异常处理时都需要用到)*方法主要功能:*1.处理当前取消节点的状态;......
  • java并发编程-AQS
    什么是AQSAQS全名:AbstractQueuedSynchronizer,是并发容器J.U.C(java.util.concurrent)下locks包内的一个类。它实现了一个FIFO(FirstIn、FirstOut先进先出)的队列。底层实现......