大家对于AbstractQueuedSynchronizer简称AQS,或许比较陌生,但是应该对可重入锁ReentrantLock,读写锁ReentrantReadWriteLock,信号量Semaphore应该比较熟悉,其实他们的底层实现就是依赖于AQS提供的服务。AQS主要功能就是提供多线程之间同步访问的功能,这里面就涉及多线程之间等待,唤醒,排队。
主要实现思路和架构:
AQS主要的实现思路,主要使用一个volatile int state(代表共享资源)和FIFO的双端队列来确保线程之间的并发等待,唤醒等功能。当前线程通过获取state状态信息,如果获取成功,那么就获得执行权直接运行,如果获取失败,那么直接将线程和状态信息,构造成一个Node节点,放入到双端队列中,阻塞当前线程。如果获取资源的线程执行执行完毕,将释放state,那么将唤醒等待的线程,去获取资源。
如图所示:
state状态获取
state状态声明是volatile,从之前的文章可以知道,该变量的修改对各个线程是可见的。对于state的操作方法,主要有getState(),setState(int newState),compareAndSetState(int expect, int update).
其中unsafe.compareAndSwapInt(this, stateOffset, expect, update);compareAndSwapInt这里采用乐观锁的技术,当多个线程尝试使用CAS去更新同一个变量时,只有一个线程成功,其他线程都会失败,但是允许失败的线程不断尝试。CAS 操作中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B。否则处理器不做任何操作。这里分别对应到stateOffset,expect,update,stateOffset初始化就是state的值。这样做效率比较高一些。
acquire(int)
tryAcquire尝试去获取锁,成功则返回true,如果失败,那么则调用addWaiter(),以独占式的方式添加到等待队列中,acquireQueued()使线程在等待队列中获取资源,如果获取到资源则返回false。如果在整个等待资源的过程中被中断过,则返回true,否则返回false。selfInterrupt()执行线程中断tryAcquire(int)
它的实现是交给子类的具体实现。AQS只是定义了一个框架,tryAcquire的实现交给子类来根据自己的策略实现,框架自身不定义。比如ReentrantLock就有自己的实现,实现具体的公平策略还是非公平策略
release(int)
release(int)方法是在独占模式下,它是AQS提供的最顶层入口,释放资源,如果彻底释放state=0,那么将会触发等待队列中的其他线程获取资源。
tryRelease()方法与tryLock()方法一样,都是由子类来实现,自身不提供实现。
unparkSuccessor(Node),主要是唤醒等待队列中的下一个线程,从代码的实现上,这里唤醒的不一定是自己的下一个节点,而是下一个可以被唤醒的线程。调用unpark()实现
介绍完独占模式下的抢占,接着介绍下共享式的抢占。
acquireShared(int)
它会获取指定arg个数量的资源,获取成功则直接返回,否则进入等待队列,直到获取到资源为止,整个过程忽略中断,tryAcquireShared依旧是由子类实现,doAcquireShared函数的意义,进入等待队列的方式,是追加到队列尾部,一直到有其他线程唤醒他,相比而言,这里保证了顺序
releaseShared(int)
共享模式下释放资源的最外层接口,tryReleaseShared接口依旧子类实现,释放资源完毕之后,唤醒等待队列中的线程,doReleaseShared()的实现,为了实现共享,他的唤醒是唤醒当前节点之后的等待节点线程。
总结
了解AQS的机制,作为java同步实现基础一个类,核心数据结构,state和双端队列了解独占模式和共享模式下的原理,为之后的学习做好准备PS:您的点赞,关注,收藏,都是给我最大的支持,谢谢