AQS是什么#Java# concurrent包中有很多阻塞类如:ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore、Synchronous、FutureTask等 , 他们的底层都是根据aqs构建的 , 它可以说是JAVA多线程编程最底层核心的抽象类 。既然这么重要 , 我们就来看看它底层原理到底是什么 。
aqs全称AbstractQueuedSynchronizer , 它作为抽象类无法单独使用 , 需要有具体实现 , 不同的实现中自己定义什么状态意味着获取或者被释放
AQS的原理是什么AQS内部维护一个先进先出(FIFO)的等待队列叫做CLH队列 , 当一个线程来请求资源时 , AQS通过状态判断是否能获取资源 , 如果不能获取 , 则挂起这个线程 , 和状态一起封装成一个Node节点放在队尾 , 等待前面的线程释放资源好唤醒自己 , 所以谁先请求的谁最先获得机会唤醒,当然新线程可能加塞提前获取资源 , 在源码解析可以看到原因

文章插图
aqs.jpg
AQS分独占和共享两种方式 , 独占模式 , 只有一个线程可以获得锁 , 比如ReentrantLock , 共享模式下可以允许多个线程同时获取锁 , 比如CountDownLatch使用的就是共享方式 ,
源码解析AQS的子类需要实现的方法
//独占方式获取资源protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();}//独占释放资源protected boolean tryRelease(int arg) {throw new UnsupportedOperationException();}//共享获取资源protected int tryAcquireShared(int arg) {throw new UnsupportedOperationException();}//共享释放资源protected boolean tryReleaseShared(int arg) {throw new UnsupportedOperationException();}//是否独占protected boolean isHeldExclusively() {throw new UnsupportedOperationException();}可以看到 , 子类调用这些方法如果没有实现的话会抛异常 , 当然也不是所有方法都要实现 , 找自己需要的实现就可以了 。为了更好的理解先实现一个最简单的锁,只需要实现tryAcquire和tryRelease方法即可
public class TestLock {private Sync sync = new Sync();//加锁public void lock(){sync.acquire(1);}//解锁public void unLock(){sync.release(1);}public static class Sync extends AbstractQueuedSynchronizer {@Overrideprotected boolean tryAcquire(int arg) {assert arg == 1;//cas将状态从0设为1 , 如何不为0则失败if(compareAndSetState(0,1)){return true;}return false;}@Overrideprotected boolean tryRelease(int arg) {assert arg == 1;if(getState() == 0){throw new IllegalMonitorStateException();}//将状态设为0setState(0);return true;}}}再来写一个并发场景 , 简单的加法 , 先获取前值 , 用sleep模拟方法执行时间比较长,然后累加public static void main(String[] args) {final AddCount count = new AddCount();ExecutorService executorService = Executors.newCachedThreadPool();for(int i = 0;i<3;i++){executorService.submit(new Runnable() {@Overridepublic void run() {try {count.add(100);} catch (InterruptedException e) {e.printStackTrace();}}});}}public static class AddCount{private int countTotle = 0;public void add(int count) throws InterruptedException {int tmp = this.countTotle;Thread.sleep(100L);this.countTotle = tmp+count;System.out.println(this.countTotle);}}//输出100100100在add方法加上自定义的的锁public static void main(String[] args) {final AddCount count = new AddCount();final TestLock testLock = new TestLock();ExecutorService executorService = Executors.newCachedThreadPool();for(int i = 0;i<3;i++){executorService.submit(new Runnable() {@Overridepublic void run() {try {testLock.lock();count.add(100);testLock.unLock();} catch (InterruptedException e) {e.printStackTrace();}}});}}//输出100200300根据这个简单的例子 , 我们来看一下源码中是怎么实现的acquirelock方法首先调用的是AQS的acquire方法
public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}它会调用tryAcquire尝试去取锁 , 如果没有取到的话调用addWaiter将Node放入队尾 , 同样也使用CAS的方式 , AQS中有大量CAS的使用 , 不了解CAS的可以看浅析乐观锁、悲观锁与CAS这里有新的线程在执行第一个判断!tryAcquire(arg)时 , 如果刚好有线程释放锁 , 那新的线程很有可能插队直接获取到锁 , 也就是有队列也无法公平的原因 。
private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 2020年做自媒体还来得及吗?
- 柯尔鸭子的智商有多高 柯尔鸭为什么能当宠物
- 家里窗户全是瓢虫怎么办 窗户上很多瓢虫
- 老白茶煮多久好喝,生茶存放多久才好喝
- 冬季男人常吃三种菜 长寿又防癌
- 大猩猩体重最重有多少斤 猩猩最大的有多少斤
- 腿上有很多硬疙瘩痒 腿上起疙瘩痒还硬硬的
- 冬季心脑血管疾病高发 要多吃保护心脏的食物
- 冬天宝宝锻炼好处多 南北方冬季运动大不同
- 托运宠物收费标准 宠物店托运多少钱
