Java AbstractQueuedSynchronizer实现原理深入分析

 

了解AQS:

AQS使用方式和其中的设计模式

继承,模板方法设计模式

梳理其中的方法

用到的模板方法:

1、独占式获取:

accquire

acquireInterruptibly//可以响应中断

tryAcquireNanos

2、共享式获取

acquireShared

acquireSharedInterruptibly

tryAcquireSharedNanos

3、独占式释放锁

release

4、共享式释放锁

releaseShared

其中需要子类覆盖的流程方法有:

独占式获取  tryAcquire

独占式释放  tryRelease

共享式获取 tryAcquireShared

共享式释放  tryReleaseShared

这个同步器是否处于独占模式  isHeldExclusively

 

同步状态state:

getState:获取当前的同步状态

setState:设置当前同步状态

compareAndSetState 使用CAS设置状态,保证状态设置的原子性

加锁和释放锁原理分析:

       显示锁、读写锁、并发工具类中conutDownLatch、semophor、ThreadPoolExcuore以及jdk1.7以前的FutureTask等都是基于Aqs来实现的,不过,1.7中的FutureTask缺点是在消除线程间的竞争时仍然保持一个中断状态,在1.8中不再使用AQS了而是基于CAS去更新status状态标志位,AQS是一个抽象类利用了模板方法设计模式,子类通过继承父类,覆盖其抽象方法 来管理相关的同步状态。AQS精妙之处是隔离了锁的设计者和实现者所关注的领域,所有尝试获取锁失败的线程都被打包成一个一个node节点竞争加入到同步队列尾部,然后进行自旋:以排他的不间断模式获取已在队列中的线程,用于条件等待方法以及获取。头结点释放锁不需要加锁,头部节点尝试加锁失败时会追加至尾部节点这个过程存在竞争,此时需加锁;。

打包成一个一个node节点竞争加入到同步队列尾部具体过程:

       首先去拿当前队列中的尾部节点,如果尾部节点不为空,这时会去通过cas尝试去快速的成为新尾部节点并返回队列节点;如果尝试失败,说明当前想成为尾部节点的线程比较多,竞争比较激烈,进入enque()死循环中进行自旋,目的是尝试成为尾部节点,分为两种情况:如果尾部节点为空,说明头结点就是尾部节点,执行设置头结点的自选方法compareAndSetHead(),如果执行成功,则赋值tail=head,完成一轮循环,继续进行下一轮死循环;否则,如果尾部节点不为空,将这个尾部节点作为当前待入队节点的前置节点,然后执行设置尾部节点的自旋方法comPareAndSetTail(),如果设置成功,尾部节点的next=当前待插节点 然后返回队列节点结束enque()自选过程,否则执行下一轮的enque自旋过程尝试成为尾部节点;

以排他的不间断模式获取已在队列中的线程,用于条件等待方法以及获取过程:

        首先拿到当前节点的前置节点,然后判断前置节点是否为头节点,如果是,继续判断模板方法内的流程方法也就是需要我们的子类实现的tryAcquire()方法,该方法内部是通过自旋设置状态标志位compareAandSetState(0,1)尝试去获取锁,获取成功则将当前线程置为独享并返回true;否则返回false;

       然后,如果这两个判断条件都满足了,则将当前节点设置为头结点,并将前置节点的next置为null,作为GC回收标识,同时从队列中移除。如果以上两个判断条件有一个失败,则执行失败后的阻塞方法shouldParkAfterAcquire(),这个里面会利用底层的工具类LockSupport.park(this)将当前线程阻塞,并返回当前线程为被中断标识。

锁的释放过程:

        首先拿到头结点,如果头结点不为空,并且头结点的等待状态不为0,执行唤醒当前头结点方法unparkSuccessor(h)来唤醒头结点中的线程,并返回true, 这里面会根据头结点线程的状态标识整形值作为判断条件,如果小于0 执行compareAndSetWaitStatus(node, ws, 0);并依次将存在的后续节点从唤醒。

 

Java AbstractQueuedSynchronizer实现原理深入分析

 

(注:以上所述是独享锁并且是非可重入的原理分析,共享锁、可重入锁将在后续更新)