[Java并发与多线程](二十一)AQS

1、为什么需要AQS——AbstractQueuedSynchronizer

1、锁和协作类的共同点闸门ReentrantLockSemaphore
2、事实上,不仅是ReentrantLockSemaphore,包括CountDownLatchReentrantReadWriteLock都有这样类似的协作或者叫同步)功能,其实,它们底层都用了一个共同的基类,这就是AQS
3、因为上面的那些协作类,它们有很多工作都是类似的,所以如果能提取出一个工具类,那么就可以直接用,对于ReentrantLockSemaphore而言就可以屏蔽很多细节,只关注它们自己的业务逻辑就可以了。

2、AQS的比喻

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

3、AQS的作用、重要性,地位

AQS的作用AQS是一个构建锁、同步器、协作工具类的工具类(框架)。有了AQS以后,更多的协作工具类都可以很方便的被写出来。一句话总结:有了AQS,构建线程协作类就容易多了
重要性,地位在重要的类中起到了地基的作用
在这里插入图片描述

4、AQS内部原理解析-AQS的三要素(*)

AQS最核心的就是三大部分
1、state:这里的state的具体含义,会根据具体实现类的不同而不同,比如在Semaphore里,它表示剩余的许可证的数量,而在CountDownLatch里,它表示还需要倒数的数量statevolatile修饰的,会被并发地修改,所以所有修改state的方法都需要保证线程安全,比如getStatesetState以及compareAndSetState操作。来读取和更新这个状态。这些方法都依赖于j.u.c.atomic包的支持;在ReentrantLock中,state用来表示"锁"的占有情况,包括可重入计数;当state的值为0的时候,表示该Lock不被任何线程所占有
2、控制线程抢锁和配合的FIFO队列:这个队列用来存放等待的线程,AQS就是排队管理器,当多个线程争用同一把锁时,必须有排队机制将那些没能拿到锁的线程串在一起。当锁释放时,锁管理器就会挑选一个合适的线程来占有这个刚刚释放的锁;AQS会维护一个等待的线程队列,把线程都放到这个队列里,这是一个双向形式的队列;
3、期望协作工具类去实现的获取/释放等重要方法
获取
a、获取操作会依赖state变量,经常会阻塞(比如获取不到锁的时候);
b、在Semaphore中,获取就是acquire方法,作用是获取一个许可证;
c、而在CountDownLatch里面,获取就是await方法,作用是等待,直到倒数结束

释放
a、释放操作不会阻塞
b、在Semaphore中,释放就是release方法,作用是释放一个许可证
c、CountDownLatch里面,释放就是countDown方法,作用是倒数一个数

5、应用实例、源码解析

AQS用法:

  1. 写一个类,想好协作的逻辑,实现获取/释放方法;
  2. 内部写一个Sync类继承AbstractQueuedSynchronizer
  3. 根据是否独占来重写tryAcquire/tryReleasetryAcquireShared(int acquires)/tryRelease(int release)等方法,在之前写的获取/释放方法中调用AQS的acquire/release或者Shared方法;

5.1、AQS在CountDownLatch的应用

  • 构造函数
  • getCount
  • countDown
  • await

总结:调用CountDownLatchawait方法时,便会尝试获取共享锁,不过一开始是获取不到该锁的,于是线程被阻塞;而共享锁可获取到的条件就是锁计数器的值为0。而锁计数器的初始值为count,每当一个线程调用该CountDownLatch对象的countDown()方法时,才将锁计数器-1。直到减为0时,前面的等待获取共享锁的线程才能继续运行。

5.2、AQS在Semaphore的应用

1、在Semaphore中,state表示许可证的剩余数量;
2、看tryAcquire方法,判断nonfairTryAcquireShared >= 0的话,代表成功;
3、这里会先检查剩余许可证数量够不够这次需要的,用减法计算,如果不够,直接返回负数——表示失败;如果够了,就用自旋加compareAndSetState来改变state状态,直到改变成功就返回整数;或者是期间如果被其他人修改了导致剩余数量不够了,那也返回负数代表获取失败。

5.3、AQS在ReentrantLock的应用

1、分析释放锁的方法tryRelease
由于是可重入的,所以state代表重入的次数,每次释放锁,先判断是不是当前持有锁的线程释放的,如果不是就抛异常, 如果是的话,重入次数就减1,如果减到了0,就说明完全释放了,于是free就是true,并且把state设置为0。
2、加锁的方法
首先判断当前state是不是等于0,也会去判断当前线程是不是目前持有锁的线程,如果都不是代表目前拿不到这把锁,于是就放到队列中等待,并在合适的时候去唤醒。

6、利用AQS实现一个自己的Latch门闩

自己用AQS实现一个简单的线程协作器:

  1. 起初门闩是关闭的,那么会有很多线程调用await方法,因为门闩是关闭的,谁调用谁就陷入等待状态;
  2. 直到后续有一个线程调用signal方法——相当于把门闩打开,此时之前所有的线程都会被释放;
  3. 使用state表示门是不是被打开,0——关闭,1——打开
public class OneShotLatch {
    private final Sync sync = new Sync();

    //共享锁
    //获取——含义和CountDownLatch一样
    public void await() {
        sync.acquireShared(0); //尝试获取锁
    }

    //释放——同countDown()
    public void signal() {
        sync.releaseShared(0);
    }

    private class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected int tryAcquireShared(int arg) {
            //state = 1代表被打开了,因为默认是0
            return (getState() == 1) ? 1 : -1;
        }

        @Override
        protected boolean tryReleaseShared(int arg) {
            setState(1);
            return true;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        OneShotLatch oneShotLatch = new OneShotLatch();

        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "尝试获取latch,获取失败那就等待");

                    oneShotLatch.await();
                    System.out.println("开闸放行" + Thread.currentThread().getName() + "继续运行");
                }
            }).start();
        }
        Thread.sleep(5000);
        oneShotLatch.signal(); //唤醒
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "尝试获取latch,获取失败那就等待");
                oneShotLatch.await();
                System.out.println("开闸放行" + Thread.currentThread().getName() + "继续运行");
            }
        }).start();
    }

}

AQS补充材料

下一章:第二十四章 获取子线程的执行结果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值