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是能回到零态的。

posted @ 2020-06-14 17:36  张还行  阅读(642)  评论(0编辑  收藏  举报