AQS是如何实现线程等待和唤醒的

AQS简介

首先我们认识下AQS(AbstractQueuedSynchronizer),是Java并发的一个框架,比如说一些并发类: 独占锁 : ReentrantLock锁(独占) 共享锁 Semaphore信号量、CoutDownLatch计数器、CyclicBarrier(循环栅栏)等... AQS核心是通过FIFO等待的队列来实现线程的阻塞和唤醒的,分别有两种队列Condition和CLH等待队列;

AQS三个关键定义

  • state:标记共享变量,volatile修饰,可重入;
arduino 复制代码
//state =0 表示当前锁没有被占用,state>0表示被占用
private volatile int state;
  • queue:线程获取锁失败后,进入queue中排队等待唤醒
  • cas:比较然后交换,修改state的值; 这里存在三个参数分别是:内存值、预期值以及新值 执行逻辑:比较内存值与预期值,如果相等则去更新新值,如果不等则返回失败;

关于AQS的队列结构,之前写过AQS为什么使用双向FIFO队列 - 掘金,这里就不做详细介绍了;

AQS加锁过程

关于加锁,主要是依靠ReentrantLock类,通过它的tryAcquire方法尝试获取锁

java 复制代码
static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }
        //尝试加锁
        protected final boolean tryAcquire(int acquires) {
        //获取当前线程对象
            final Thread current = Thread.currentThread();
            //获取当前state值
            int c = getState();
            //判断是否被占用
            if (c == 0) {
            //多个线程请求锁,利用cas来更新
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                //更新成功说明就获取到了锁对象,设置当前线程持有锁对象
                    setExclusiveOwnerThread(current);
                    //获取锁成功
                    return true;
                }
            }
            //如果当前线程已经持有锁,因为可重入,可以继续state ++
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                //可重入锁,获取成功
                return true;
            }
            //没有获取锁成功,返回false
            return false;
        }
    }

addWaiter

scss 复制代码
public final void acquire(int arg) {
//获取锁失败,调用addWaiter进入等待队列
   if (!tryAcquire(arg) &&
       acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
       selfInterrupt();
}

如果返回false加锁失败,直接就丢弃么,不是的,需要加入到queue中等待队列进行等待,静态内部类Node的addWaiter方法;

ini 复制代码
 private Node addWaiter(Node mode) {
 //将当前线程封装成Node
        Node node = new Node(Thread.currentThread(), mode);
       //获取尾节点
        Node pred = tail;
        //不为空
        if (pred != null) {
        //将prev节点设置为tail尾节点
            node.prev = pred;
            //进行cas,尾节点和tail进行交换
            if (compareAndSetTail(pred, node)) {
            //tail的next节点变成node
                pred.next = node;
                return node;
            }
        }
        //入队不成功 执行enq方法
        enq(node);
        return node;
    }

enq方法

ini 复制代码
    private Node enq(final Node node) {
    //死循环
        for (;;) {
        //将node设置为尾节点
            Node t = tail;
            //如果为空
            if (t == null) { 
            //创建新的Node新节点,设置为head
                if (compareAndSetHead(new Node()))
                //经过循环node将到队列尾部
                    tail = head;
            } else {
                node.prev = t;
                //cas将node设置为tail
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

LockSupport

park阻塞

上面说了加锁失败的线程都将进入队列等待,这时候就出现了一个park的概念,LockSupport类的park方法,对AQS中的node线程进行阻塞,阻塞的主要目的是:防止自旋一直获取锁消耗CPU的资源,需要的时候可以调用unpark方法唤醒线程;

scss 复制代码
    public static void park(Object blocker) {
    //获取当前线程
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        //调用UNSAFE的park方法,将线程进入休眠等待状态
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }

unpark唤醒

唤醒需要被唤醒的线程

arduino 复制代码
    public static void unpark(Thread thread) {
    //传入需要被唤醒的线程
        if (thread != null)
            UNSAFE.unpark(thread);
    }

Condition队列

上面说了线程的阻塞和唤醒,那么需要被阻塞和唤醒的线程要是需要搭载在队列上面的,所以AQS就提供了Condition队列;

Wait

重要的两个节点condition的头节点和尾节点

java 复制代码
//condition对应的队列的头节点
private transient Node firstWaiter;
//condition对应的队列的尾节点
private transient Node lastWaiter;

await主要是是将node阻塞线程进行入列操作

scss 复制代码
        public final void await() throws InterruptedException {
        //线程中断抛出异常
            if (Thread.interrupted())
                throw new InterruptedException();
                //加入等待队列
            Node node = addConditionWaiter();
            //将锁释放 如果释放失败就将waitstatus状态改完CANCELLED
            long savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
            //进行阻塞
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
              //已经唤醒的线程,需要被提出队列
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

unlinkCancelledWaiters剔除不为conditon节点的node

ini 复制代码
        private void unlinkCancelledWaiters() {
        //获取等待队列
            Node t = firstWaiter;
            Node trail = null;
            //不为空就一直循环
            while (t != null) {
                Node next = t.nextWaiter;
                //waitStatus不为CONDITION队列
                if (t.waitStatus != Node.CONDITION) {
                 //切断不为condition节点的下一个节点,切断它们之间的关系
                    t.nextWaiter = null;
                    //如果trail 为空,firstWaiter也不是CONDITION节点,需要被剔除
                    if (trail == null)
                        firstWaiter = next;
                    else
                        trail.nextWaiter = next;
                    if (next == null)
                        lastWaiter = trail;
                }
                else
                    trail = t;
                t = next;
            }
        }

Signal

前面说线程加入等待队列,下面来看线程是如何被唤醒的

java 复制代码
public final void signal() {
//判断当前线程是否持有锁
    if (!isHeldExclusively())
// 没有持有就抛出异常
        throw new IllegalMonitorStateException();
//获取condition里面第一个节点
    Node first = firstWaiter;
//如果不为空 调用doSignal
    if (first != null)
        doSignal(first);
}

transferForSignal方法

typescript 复制代码
private void doSignal(Node first) {
            do {
            //循环,如果队列的第一个和下一个为空,则将后续节点都剔除
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
                //不为空调用transferForSignal方法
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

transferForSignal节点唤醒

arduino 复制代码
     final boolean transferForSignal(Node node) {
     
     //将nodewaitStatus从CONDITION改为0 
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
         //返回firstWaiter前节点
        Node p = enq(node);
        int ws = p.waitStatus;
        // 如果前节点的waitStatus > 0
        //或者将前节点waitStatus改成 Node.SIGNA 失败了,也会触发节点唤醒
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

SignalAll(唤醒condition全节点)

方法大致差不多,就是调用了signalAll方法

java 复制代码
public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first);
}

doSignalAll

ini 复制代码
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}

checkInterruptWhileWaiting(唤醒后的操作)

唤醒后干嘛呢,要看这里了,checkInterruptWhileWaiting方法

checkInterruptWhileWaiting这一步是检查唤醒线程的状态,根据不同的状态进行设置异常或者中断

arduino 复制代码
            private int checkInterruptWhileWaiting(Node node) {
           
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }
  • 如果线程中断,设置为/THROW_IE/ ,抛出异常
  • 如果signal没有发生异常,则设置为/REINTERRUPT/
  • 如果线程没有中断就返回0

transferAfterCancelledWait

java 复制代码
            final boolean transferAfterCancelledWait(Node node) {
//将node的WaitStatus从Node.CONDITION改为0
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            enq(node);
            return true;
        }
  
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }

acquireQueued抢夺锁

ini 复制代码
        final boolean acquireQueued(final Node node, long arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                //如果Node是头节点,就去尝试获取锁
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    //帮助gc
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
        // 如果抢占锁失败,就将线程从队列清除
            if (failed)
                cancelAcquire(node);
        }
    }
  • acquireQueued不出现异常的话,就将interruptMode设置为/REINTERRUPT/
  • node.nextWaiter != null ,就是没有经过唤醒,就去清除它
  • interruptMode !=0 调用reportInterruptAfterWait方法

interruptMode为异常状态就抛出异常,状态为REINTERRUPT,调用中断方法

java 复制代码
private void reportInterruptAfterWait(int interruptMode)
    throws InterruptedException {
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();
}

总结

基于可重入锁ReentrantLock加锁过程,出现的condition线程等待队列,在队列中根据node节点状态,来决定阻塞还是被唤醒,释放锁唤醒queue上的其他节点来争夺锁,线程唤醒后会在condition队列中进行剔除,将waitStatus状态改为0,加锁锁的queue;

相关推荐
陈平安Java and C5 小时前
MyBatisPlus
java
秋野酱5 小时前
如何在 Spring Boot 中实现自定义属性
java·数据库·spring boot
安的列斯凯奇6 小时前
SpringBoot篇 单元测试 理论篇
spring boot·后端·单元测试
Bunny02126 小时前
SpringMVC笔记
java·redis·笔记
架构文摘JGWZ6 小时前
FastJson很快,有什么用?
后端·学习
BinaryBardC6 小时前
Swift语言的网络编程
开发语言·后端·golang
feng_blog66886 小时前
【docker-1】快速入门docker
java·docker·eureka
邓熙榆6 小时前
Haskell语言的正则表达式
开发语言·后端·golang
枫叶落雨2228 小时前
04JavaWeb——Maven-SpringBootWeb入门
java·maven
m0_748232398 小时前
SpringMVC新版本踩坑[已解决]
java