Java并发编程—锁机制之抽象同步队列AQS

本文深入探讨了Java并发编程中的抽象同步队列AQS,它是JUC中锁实现的基础。AQS是一个FIFO双向队列,内部包含Node节点用于存储线程并维护状态。文章介绍了AQS的主要方法,如acquire和release,以及条件变量ConditionObject,展示了如何实现线程同步和资源管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 概述

AQS是AbstratQueuedSynchronizer的缩写,即抽象同步列,JUC中锁的底层就是基于该抽象类实现的。其类图如下:

 该类是一个FIFO的双向队列,队列中的元素时Node(是AQS的静态内部类)。

Node中的thread变量用来存放进入AQS的线程,Node中的SHARE用来标记该线程是获取共享资源时被挂入AQS的;EXCLUSIVE是用来标记该线程是获取独占资源时被挂入AQS的;waitStatus可以用来表示该线程的状态:线程被取消了CANCELLED,线程被挂在条件队列上CONDITION,线程需要被唤醒SIGNAL,释放共享资源时需要通知其他节点PROPAGATE;prev用来记录当前节点的前驱结点;next用来记录当前节点的后驱节点。

ConditionObject是AQS的内部类,是一个条件变量,用来结合锁实现线程同步,后面将详细介绍。

AQS中还有一个状态变量state,该变量可以用来表示锁的一些状态,比如对于独占锁ReentrantLock,state可以用来表示其可重入次数,对于ReentrantReadWriteLock来说,state的高十六位表示读状态,也就是读锁的获取次数,低十六位用来表示写锁的可重入次数。

2 主要方法

线程同步的关键就是对state状态变量进行操作,而操作state又可以分为独占方式和共享方式,独占方式下获取和释放资源的方式是:void acquire(int arg), void acquireInterruptibly(int arg), boolean release(int arg);共享方式下获取和释放资源的方式是void acquireShared(int arg), void acquireSharedInterruptibly(int arg), boolean releaseShared(int arg)。下面主要对独占方式下的acquire(int arg)方法和release(int arg)方法进行介绍。

2.1 acquire(int arg)

首先来看以下该方法的源码,首先尝试获取独占资源,获取失败则将自己挂到阻塞队列尾部

public final void acquire(int arg) {
            //tryAcquire()是尝试获取资源的方法,由AQS具体的子类来实现
            if (!tryAcquire(arg) &&//tryAcquire()尝试获取资源失败进入以下判断逻辑
                    acquireQueued(addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg))
                selfInterrupt();
        }

然后再接着看addWaiter()方法的源码

private AbstractQueuedSynchronizer.Node addWaiter(AbstractQueuedSynchronizer.Node mode) {
            //创建状态为mode(EXCLUSIVE)的节点,并将当前线程放入node中
            AbstractQueuedSynchronizer.Node node = new AbstractQueuedSynchronizer.Node(Thread.currentThread(), mode);
            // Try the fast path of enq; backup to full enq on failure
            AbstractQueuedSynchronizer.Node pred = tail;//获取当前AQS队列的尾部节点
            if (pred != null) {//当前尾部节点不是null
                node.prev = pred;//将要添加的node的前驱节点设置为pred
                if (compareAndSetTail(pred, node)) {//将当前AQS队列的tail指针指向node
                    pred.next = node;//将pred的后驱节点设置为node
                    return node;//返回该node
                }
            }
            //若此时的尾节点为空,则说明此时AQS中队列没有元素,则执行以下方法将node添加到队列中
            enq(node);
            return node;
        }

        private AbstractQueuedSynchronizer.Node enq(final AbstractQueuedSynchronizer.Node node) {
            for (;;) {//自旋操作
                //t指针指向AQS尾节点tail
                AbstractQueuedSynchronizer.Node t = tail;
                if (t == null) { /*若尾节点为null则说明当前队列中没有元素
                    (这里注意虽然addWaiter()中判断了一次,但由于程序执行过程中并没有加锁,
                    故在此处需要再次判断,防止期间有其他线程添加了节点)*/
                    //利用CAS操作设置头结点(该节点是一个虚节点,或者说哨兵节点,内部并没有相应线程)
                    if (compareAndSetHead(new AbstractQueuedSynchronizer.Node()))
                        tail = head;//将tail也指向哨兵节点后进入下一次循环(此时还未将真正要添加的节点添加)
                } else {//在第二次循环时开始添加node
                    node.prev = t;//将node的前驱节点设置为t,此时t指向哨兵节点
                    if (compareAndSetTail(t, node)) {//利用CAS设置node为尾节点
                        t.next = node;//将t(此时指向哨兵节点)的后驱节点设置为node
                        return t;//返回t
                    }
                }
            }
        }

至此,我们就已经将尝试获取独占资源失败的节点放入到了AQS阻塞队列的尾部,下面再来看外层的acquireQueued()方法:

final boolean acquireQueued(final AbstractQueuedSynchronizer.Node node, int arg) {
            boolean failed = true;
            try {
                boolean interrupted = false;
                for (;;) {//自旋操作
                    final AbstractQueuedSynchronizer.Node p = node.predecessor();//获取该节点的前驱节点
                    if (p == head && tryAcquire(arg)) {//如果该节点的前驱节点是哨兵节点且请求资源成功则进入以下逻辑
                        setHead(node);//将该节点设置为哨兵节点,具体操作看源码一目了然
                        /*private void setHead(Node node) {
                            head = node;
                            node.thread = null;
                            node.prev = null;
                         }
                        * */
                        p.next = null; // help GC
                        failed = false;
                        return interrupted;
                    }
                    //上述逻辑判断失败则进入下面的判断逻辑
                    // shouldParkAfterFailedAcquire()是根据当前节点前驱节点的状态判断是否阻塞该节点
                    //parkAndCheckInterrupt()则是阻塞该节点
                    if (shouldParkAfterFailedAcquire(p, node) &&
                            parkAndCheckInterrupt())
                        interrupted = true;
                }
            } finally {
                if (failed)//请求资源失败,当前节点放弃获取资源
                    cancelAcquire(node);
            }
        }

2.2 release(int arg)

来看看该方法的源码:

public final boolean release(int arg) {
            //尝试释放独占资源,该方法也是由AQS具体子类来实现
            if (tryRelease(arg)) {
                //获取当前队列的头结点
                AbstractQueuedSynchronizer.Node h = head;
                //激活AQS队列中的一个线程
                if (h != null && h.waitStatus != 0)
                    unparkSuccessor(h);
                return true;
            }
            return false;
        }

3 条件变量支持ConditionObject

AQS中的条件变量就和synchronized(object)中的object一样,内部有两个重要的方法signal()和await()方法,就好比synchronized中的notify()和wait()方法一样;调用条件变量await()方法的线程会被挂到条件变量的阻塞队列中(这是一个单向链表),调用条件变量signal()方法的线程会唤醒该条件变量阻塞队列中的一个线程。下面来看一个例子:

public class Test {
    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " 调用条件变量的await()方法被阻塞了.....");
            try {
                condition.await();
                System.out.println(Thread.currentThread().getName() + " 被唤醒了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        thread.start();
        try {
            Thread.sleep(1000);
            System.out.println("main尝试获取锁");
            lock.lock();//注意使用条件变量是一定要先获取该条件变量所对应的锁,否则会抛出IllegalMonitorStateException异常
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

        System.out.println("main is over");
    }
}

整体过程就是thread首先获取锁后调用条件变量的await()方法将自己挂到condition的条件队列上并释放了锁,然后main线程获得所后调用condition的signal()方法,唤醒了一个条件队列上的线程,也就是thread(注意,此时还是main线程获取了锁,thread从条件队列移动到了AQS阻塞队列上等待获取资源),当main线程释放锁后,thread获取到锁后继续执行后面的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值