AQS详解

什么是AQS?

AQS:AbstractQueuedSynchronizer。 是一个用来构建锁和同步器的框架,许多同步类实现都继承了AQS,如常用的ReentrantLock/Semaphore/CountDownLatch…

在这里插入图片描述
在这里插入图片描述

由以上两图可以看出,基本上所有的并发工具类都实现了AQS,可见AQS的重要性

为什么需要AQS?

  • 锁和协作类都有一个共同点:闸门
  • ReentrantLock和Semaphore,包括CountDownLatch、ReentrantReadWriteLock都有类似“协作”或者叫“同步”的功能,其实,他们底层都用了一个共同的基类,就是AQS

Semaphore源码:
在这里插入图片描述
CountDownLatch源码:
在这里插入图片描述
ReentrantLock源码同样如此。。。

AQS的作用

  • AQS是一个用于构建锁、同步锁、协作工具类的工具类。有了AQS以后,更多的协作工具类都可以很方便的被写出来了
    一句话总结:有了AQS,构建携程协作类就容易多了

AbstractQueuedSynchronizer是Doug lea写的,从JDK1.5加入的一个基于FIFO等待队列实现的一个用于实现同步器的基础框架,我们用IDE看AQS的实现类源码,可以发现实现类有一下这三个核心:

  1. state
  2. 控制线程抢锁和配合的FIFO队列
  3. 期望协作工具类去实现的获取/释放等重要方法

state状态

源码中就是一个被volatitle修饰的int值
在这里插入图片描述

  • 这里的state的具体含义,会根据具体实现类的不同而不同,比如在Semaphore里,它表示“剩余的许可证数量”,而在CountDownLatch里,他表示“还需要倒数的数量”
  • state是volatile修饰的,会被并发的修改,所以所有修改state的方法都需要保证线程安全,比如getState、setState以及compareAndSetState操作来读取和更新这个状态。这些方法都依赖于j.u.c.atomic包的支持
  • 在ReentrantLock中,state用来表示锁的占用情况,包括可重入计数,当state的值为0的时候,标识改Lock不被任何线程所占用

FIFO队列

  • 这个队列用来存放“等待的线程”,AQS就是“排队管理器”,当多个线程挣用同一个锁时,必须有排队机制将那些没能拿到锁的线程串在一起。当锁释放时,锁管理器就会挑选一个合适的线程来占有这个刚释放的锁
  • AQS会维护一个等待的线程队列,把线程都放到这个队列里
  • 这是一个双向形式的队列
    在这里插入图片描述

期望协作工具类去实现的获取/释放等重要方法

  • 这里的获取和释放方法,是利用AQS的协作工具类里最重要的方法,是由协作类自己去实现的,并且含义都不同
获取方法
  • 获取操作会依赖state变量,经常会阻塞(比如获取不到锁)
  • 在Semaphore中,获取就是acquire方法,作用是获取一个许可证
  • 在CountDownLatch里,获取就是await方法,作用是“等待,知道倒数结束”。在CountDownLatch里,await会判断state是否为0,如果state不为0,线程就会陷入阻塞状态

AQS应用实例、源码解析

  • 第一步:写一个类,想好协作的逻辑,实现获取/释放方法
  • 第二部:内部写一个Sync类继承AbstractQueuedSynchronizer
  • 第三部:根据是否独占来重写tryAcquire/tryRelease或tryAcquireShared(int acquires)和tryRelaseShared(int releases)等方法,在之前写的获取/释放方法中调用AQS的acquire/release或者Shared方法

CountDownLatch源码分析

  • 内部类Sync继承AQS
    在这里插入图片描述

AQS在CountDownLatch中的应用

1 构造函数
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在源码中我们可以看出, 底层将count值赋给了state

2.getCount()
在这里插入图片描述
在这里插入图片描述
这个方法也只是返回了state值

3.await()
在这里插入图片描述
在这里插入图片描述
如果线程中断,则抛中断异常。然后判断tryAcquireShared返回值是否小于0,如果小于0,则执行doAcquireSharedInterruptibly方法;
在这里插入图片描述
这个方法意思是,如果state==0,返回1,state!=0,返回-1,按照上面的方法来看,如果state!=0,则会执行doAcquireSharedInterruptibly方法(这个方法是让线程阻塞),如果返回1,就会直接跳过,线程就会继续执行。
doAcquireSharedInterruptibly,这个方法基本作用是将当前线程放入等待队列,并且让他进入阻塞状态。
在这里插入图片描述
源码中的node.predecessor()方法会将线程包装成一个Node节点,这个Node节点就是等待队列中的节点,如下图:
在这里插入图片描述
parkAndCheckInterrupt方法会让线程阻塞:
让我们看下源码:
在这里插入图片描述
其中主要是park方法:
在这里插入图片描述
park里面有一个UNSAFE操作:
在这里插入图片描述
这个park是个native方法,这个操作主要就是让线程挂起,进入阻塞状态。

4.countDown()
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们来分析一下这个源码:
首先获取state值,如果state0,说明已经有人将其释放了,直接返回false;否则通过cas操作,将state-1,然后判断修改过后的state值是否为0,将结果返回,如果修改后的state0,就返回true,否则返回false。我们再看releaseShared这个方法,如果tryReleaseShared方法返回的是true(state==0),执行doReleaseShared方法(这个方法主要是将线程由等待状态唤醒),否则返回false,线程继续阻塞,直到state变为0。

AQS在CountDownLatch的总结

  • 调用CountDownLatch的await方法时,便会尝试获取“共享锁”,不过一开始是获取不到该锁的,于是线程被阻塞
  • 而共享锁可获取的条件,就是锁计数器的值为0,而锁计数器的初始值为count,每当一个线程调用该CountDownLatch对象的countDown方法时,才将锁计数器-1
  • count个线程调用countDown之后,锁计数器才为0,而前面提到的线程获取共享锁的线程才能继续运行
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值