AQS (AbstractQueuedSynchronizer)原理

AQS 原理

AbstractQueuedSynchronizer 抽象同步队列简称 AQS,它是实现同步器的基础组件,类图如下:
AQS 类图
由上图可知,AQS 是一个 FIFO 的双向队列,其内部通过节点 headtail 记录的队首和队尾元素,队列元素的类型为Node。其中Nodethread 变量来存放进入AQS 队列里面的线程。Node 内部的 SHARED 用来标记该线程是获取共享资源时被阻塞挂起后放入 AQS 队列中,EXCLUSIVE 是用来标记线程是获取独占资源时被挂起后放入AQS 队列的; waitStatus 记录当前线程等待状态,可以为 CANCELLED(线程被取消了),SIGNAL(线程需要被唤醒),CONDITION(线程在条件队列里面等待),PROPAGATE(释放共享资源时需要通知其他节点);prev 记录当前节点的前驱节点,next 记录单前节点的后继节点。

state 作用

AQS 中维持了一个单一的状态信息 state 可以通过 getState() , setState(int newState),compareAndSetState(int expect, int update) 修改其值。

  1. 对于 ReentrantLock 来说,state 可以用来表示当前线程获取锁的可重入次数
  2. 对于 ReentrantReadWriteLock 来说,state 的高16为表示读状态,也就是获取该读锁的次数,低16位表示获取到写锁的线程的可重入次数。
  3. 对于 Semaphore 来说,state 是用来表示可用信号的个数。
  4. 对于 CountDownLatch 来说,state 用来表示计数器当前的值。

ConditionObject 作用

AQS 有个内部类 ConditionObject, 用来结合锁实现线程同步。ConditionObject 可以直接访问 AQS 对象内部的变量,比如 state 状态值和 AQS 队列。ConditionObject 是条件变量,每个条件变量对应一个条件队列(单向链表队列),其用来存放调用条件变量的await 方法后被阻塞的线程。如类图所示,这个条件度列的头、尾元素分别为 firstWaiterlastWaiter

对于 AQS 来说,线程同步的关键是对状态值 state 进行操作。根据 state 是否属于一个线程,操作 state 的方式分为独占方式和共享方式。

独占方式,获取与释放资源的流程

使用独占方式获取的资源时与具体线程绑定的,如果一个线程获取到了资源,就会标记是这线程获取到了,其它线程再尝试操作 state 获取资源时,会发现当前资源不是自己持有,就会在获取失败后被阻塞。如:ReentrantLock 的实现,当一个线程获取了 ReentrantLock 的锁后,在AQS 内部会首先使用CAS 操作将 state 状态值从0 改为1,然后设置当前锁的持有者为当前线程,当前线程再次获取锁时发现它就是锁的持有者,则会把 state 状态值从1改为2,也就是设置锁的重入次数。而当另外一个线程获取锁时发现自己并不是该锁的持有者就会被放入 AQS 阻塞队列后挂起

acquire(int arg) 获取资源

当一个资源调用 acquire(int arg) 方法获取独占资源时,首先使用 tryAcquire(arg) 方法尝试获取资源。

 public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

release(int arg) 释放资源

当一个线程调用 release(int arg) 方法时,首先使用 tryRelease(arg) 操作释放资源。

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

独占锁总结

继承自AQS 实现的独占锁 ReentrantLock, 定义当 state 为 0 时表示锁空闲,为1时表示锁已经被占用。在重写 tryAcquire(int acquires) 时,在内部需要使用CAS 算法查看当前state 是否为0,如果为0则使用CAS 设置为1,并设置当前锁的持有者为当前线程,而后返回true,如果CAS 失败则返回false。在重写 tryRelease(int releases) 时,在内部需要使用CAS 算法把 state 的值从1改为0,并设置当前锁的持有者为 null,然后返回true,CAS 操作失败则返回false。

共享方式,获取与释放资源的流程

共享方式的资源与具体线程是不相关的,当多个线程去请求资源时通过CAS 方式去竞争获取资源,当一个线程获取到了资源后,另外一个线程再次去获取时如果当前资源还能满足它的需要,则当前线程只需要使用CAS 方式进行获取。比如 Semaphore 信号量,当一个线程通过 acquire(int arg) 获取信号量时,会首先看当前信号量个数是否满足需要,不满足则把当前线程放入阻塞队列,如果满足则通过自旋 CAS 获取信号量。

acquireShared(int arg) 获取资源

当线程调用 acquireShared(int arg) 获取共享资源时,会首先使用 tryAcquireShared(int arg) 尝试获取资源,设置state的值,成功则直接返回,失败则将当前线程封装类型为Node.SHAREDNode节点后插入到 AQS 阻塞队列的尾部,并使用LockSupport.park(this) 方法挂起自己。

 public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

releaseShared(int arg) 释放资源

当一个线程调用 releaseShared(int arg) 时会尝试使用tryReleaseShared 操作释放资源,这里设置状态变量 state 的值,然后使用LockSupport.unpark(thread) 激活AQS队列里面被阻塞的一个线程。被激活的线程则使用 tryReleaseShared 查看当前状态变量state 的值是否能满足自己的需要,满足则该线程被激活,然后继续向下运行,否则还是会被放入AQS队列并被挂起。

  public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

注意点

AQS 没有提供可用的 tryAcquireShared(int arg)tryReleaseShared(int arg) 方法,正如 AQS 是锁阻塞和同步器一样,tryAcquireSharedtryReleaseShared 需要由具体的子类来实现。子类在实现 tryAcquireSharedtryReleaseShared 是要根据具体场景使用 CAS 算法尝试修改 state 状态值,成功则返回true,否则返回false。如继承自AQS 实现的读写锁 ReentrantReadWriteLock 里面的读锁在重写 tryAcquireShared 时,首先查看写锁是否被其他线程持有,如果是则直接返false,否则使用CAS 递增 state 的高16位,读锁在重写 tryReleaseShared 时,在内部需要使用CAS 算法把当前state 的值高16位减1,成功则返回true,失败则返回false。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值