JAVA面试中常问到的AQS了解一下!

AQS:AbstractQueuedSynchronized抽象队列同步器,底层大多依赖于CAS(可做乐观锁,参见我之前的https://www.wukong.com/question/6665609704179253507/)AQS真正强大的原因是,实现AQS类中的几个方法就可以简单快速的得到性能强大的锁(已经实现的有ReentrantLock,CountDownLatch等常用的多线程并发技术)

下面来看下AQS的核心点:

1,一个内部类Node:定义了一个双端双向链表(双端:head头,tail尾,双向:prev,next),Node中存放数据和前后节点的指针,这就是AbstractQueuedSynchronized中的Queue !

2,status:标注了锁的状态,主要使用方法compareAndSetState进行锁状态的改变, compareAndSetState方法主要使用unSafe的cas方法,保证状态改变的原子性!

AQS支持排它锁和共享锁两种实现,可通过重写的方法进行区分锁类型:

1,独占锁:重写tryAcquire(获取共享资源),tryRelease(释放),isHeldExclusively(状态)

2,共享锁:重写tryAcquireShared(),tryReleaseShared(),isHeldExclusively

上面的方法中带Acquire的方法是试图获取共享资源,根据返回值判断是否成功(true,false或者是否<0),带Release的方法试图释放共享资源,在队列中等待的节点就有机会获取到锁,isHeldExclusively:是否独占的状态方法;

下面分析下AQS中的主要方法: ①,acquire方法,源码截图如下:

可以看到比较重要的方法为tryAcquire,addWaiter,acquireQueued,分别解释如下:

tryAcquire:这个就是用户自己实现的获取资源的操作,成功返回true !(reentrantLock中就是简单的传了一个1,当做锁)

addWaiter:加入队列的方法,存在队尾的时候,将原来的队尾(tail)指向新的node,新的node设置为tail(设置使用了CAS保证原子性),并返回;如果不存在队尾,或者上面的设置队尾失败(没有返回),则使用enq方法自旋操作 (for (;;))保证一定成功,如果没有队尾,则新node设置为队头和尾,否则直接设置为队尾(都是用CAS操作),代码截图如下:

acquireQueued:不断自旋,获取当前node的前任(上个node),前任如果是队头,则尝试获取锁,如果成功,则把当前节点设置为队头,同时之前的头解链(p.next = null; // help GC)

shouldParkAfterFailedAcquire方法判断是否应该休息会,如果可以休息,则调用parkAndCheckInterrupt阻塞,等待下一次被前任唤醒;

整个acquire方法流程如下图:

②,release方法:源码截图如下: tryRelease:同样的需要用户实现的方法,释放资源!unparkSuccessor:将当前node的状态置零,获取到下一个node,如果下一个node为null或者不为null,但是已经失效(s.waitStatus > 0),则从队尾循环到队头,找到最后一个有效的node(t.waitStatus <= 0),然后唤醒;

上面分析了独占锁类型使用的重要方法,而共享锁源码比较类似,就不作分析了!

AQS将内部实现(node,双端双向链表,CAS操作等)进行封装,用户只需要继承

AbstractQueuedSynchronized实现方法即可得到锁,比如ReentrantLock基本用到的就是上面描述的方法实现了一个可重入锁;

使用几十行代码自己轻松实现一个ReentrantLock,可以看到结果使用0循环30*3000次,加锁和不加锁的结果截然不同;具体源码贴出来太乱,可私信我获取!

最近一直在分享java相关的技术,也是为了自己能巩固学过的知识,有需要的朋友,可以关注,谢谢!

举报
评论 0