• JUC:AQS介绍、AQS原理、AQS底层用模板方法模式、定义两种资源共享方式



    AQS


    AQS介绍

    java.util.concurrent.locks 包中的 AbstractQueuedSynchronizer (抽象队列同步器)类

    该类是用于构建锁和同步器的框架。 使用该类可以简单且高效的构造出应用广泛的同步器,比如:ReentrantLock, Semaphore, ReentrantReadWriteLock, FutureTask等。同样我们也能继承该类,去实现自己需求的同步器。


    AQS原理

    AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程归为有效工作线程,并且该资源设置为锁定状态。其他线程如果要请求该共享资源,由于该资源被占有,因此无法请求成功,那么就需要一套线程阻塞等待以及唤醒时锁分配的机制。这个机制AQS使用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

    在AbstractQueuedSynchronizer类中有介绍CLH(Craig, Landin, and Hagersten),CLH队列是一个虚拟的双向队列(虚拟的双向对立是不存在队列实例,而是通过Node节点之间的关系关联)

    AQS原理图:

    在这里插入图片描述

    AQS使用int类型的state标示锁的状态。使用CAS对同步状态进行原子性操作实现状态修改。

    private volatile int state;  // The synchronization state. volatile保证线程可见
    

    获取同步状态的源代码:

    // 获取同步的状态
    protected final int getState() {
            return state;
        }
    // 设置同步的状态
    protected final void setState(int newState) {
        state = newState;
    }
    // 通过CAS设置同步的状态
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
    

    AQS中Node节点常量含义

    volatile int waitStatus;
    
    int CANCELLED =  1;//waitStatus值为1时表示该线程节点已释放(超时、中断),已取消的节点不会再阻塞。
    int SIGNAL    = -1;//waitStatus为-1时表示该线程的后续线程需要阻塞,即只要前置节点释放锁,就会通知标识为 SIGNAL 状态的后续节点的线程 
    int CONDITION = -2; //waitStatus为-2时,表示该线程在condition队列中阻塞(Condition有使用)
    int PROPAGATE = -3;//waitStatus为-3时,表示该线程以及后续线程进行无条件传播(CountDownLatch中有使用)共享模式下, PROPAGATE 状态的线程处于可运行状态 
    waiteStatus 为 0:          None of the above // 初始状态
    

    AQS定义两种资源共享方式

    Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:

    • 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁

    • 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的

    Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock的Read锁。

    不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在底层实现好了。


    AQS底层用模板方法模式

    AQS底层是模板方法模式的,如果需要自定义同步器,一般方法是:继承AQS,并重写指定方法(无非是按照自己定义的规则对state的获取与释放);将AQS组合在自定义同步组件的实现中,并调用模板方法,而这些模板方法会调用重写的方法。

    需要重写的方法:

    isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
    tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
    tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
    tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
    tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。
    

    以上方法默认抛出UnsupportedOperationException异常,AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。

    以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

  • 相关阅读:
    Linux新手入门:通过chmod改变文件权限
    Android Activity 以及 Application 生命周期
    java 并发库之 Executors
    java Netty tcp通讯
    Android App 切换语言
    java svg转png
    高效的数独算法-位运算
    Android 高效的`InjectView – ButterKnife`
    Android 监听文件夹
    Android 更新UI
  • 原文地址:https://www.cnblogs.com/turbo30/p/13688200.html
Copyright © 2020-2023  润新知