AbstractQueuedSynchronizer源码剖析(一)- 从抽象和接口说起

来源:互联网 发布:大数据怎么搜索 编辑:程序博客网 时间:2024/06/06 02:43

1、从设计思想开始

      JDK1.5之后引入了并发java.util.concurrent,大大提高了Java程序的并发性能。在介绍Lock之前,我们需要先熟悉一个非常重要的组件,掌握了该组件JUC包下面很多问题都不在是问题了。AQS是构建锁或者其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),JUC并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。它是JUC并发包中的核心基础组件。
     AQS从设计之初(即仅单单的从AQS本身来说),它仅仅只是提供独占锁和共享锁两种方式,在这里请大家牢记,从AQS本身来说,是不存在所谓的公平与非公平性。AQS基于模板模式设计,因为从API的设计之初,作者(Doug Lea)已经预订了任何一个子类只能支持AQS当中的独占锁和共享锁当中的一种。所以AQS没有抽象方法,所有方法都有默认实现,这有区别于传统的模板模式。作者这样做的目的是让子类更加清洁,不需要实现无关的抽象方法。
      AbstractQueuedSynchronizer继承自AbstractOwnableSynchronizer。AbstractOwnableSynchronizer简称AOS,AOS其实相对来说是比较简单的,AOS里面只有一个属性,那就是exclusiveOwnerThread,也就是用来标识当前占有锁的线程。另外还有2个方法,分别用来get和set这个exclusiveOwnerThread。作者为什么需要将持有锁的线程的标识向上抽取?这是值得我们思考的。在JDK的源码中有对AQS这样的一段描述:
  • A synchronizer that may be exclusively owned by a thread.  This class provides a basis for creating locks and related synchronizers that may entail a notion of ownership.  The AbstractOwnableSynchronizer class itself does not manage or use this information. However, subclasses and tools may use appropriately maintained values to help control and monitor access and provide diagnostics.
简单翻译如下:同步器是需要被线程互斥访问的。AOS提供了一个基本的概念,那就是创建锁时赋予一个对于这个锁的所有权。AOS本身并不会去管理或者去使用这些信息。然而子类或者其他工具类或许会在适当的时候去维护这些信息用来控制和监听访问控制权。AOS源码如下,为了阅读方便,我去掉了源码中的注释,但是我强烈建议你一定要记得去阅读它,这样你才能从框架的设计者口中得到最准确的关于这个类或者接口的设计说明。
public abstract class AbstractOwnableSynchronizer implements java.io.Serializable {    protected AbstractOwnableSynchronizer() { }    private transient Thread exclusiveOwnerThread;    protected final void setExclusiveOwnerThread(Thread t) {        exclusiveOwnerThread = t;    }    protected final Thread getExclusiveOwnerThread() {        return exclusiveOwnerThread;    }}
到了这里,我们需要回答一个问题,那就是作者为什么需要将持有锁的线程的标识向上抽取?其实原因是很简单的。AQS诞生与JDK1.5,而AOS是在JDK1.6才出现的。也就是说在整个AQS的生命过程中,都没有用到AOS中声明的属性或方法,这些属性或方法是在AQS的子类中才用到的。也就是说,在JDK1.6以后,Doug Lea对AQS的子类实现做出了增强。那么Doug Lea为什么不直接把AOS中声明的属性或方法直接放在AQS中?或许Doug Lea认为如果这样做,是对AQS的一种侵入,因为AQS根本不需要这些,所以就往上抽取了一层。

2、AbstractQueuedSynchronizer暴露的API

在本文中,我不想谈到太多的关于AQS的实现细节,这些是接下来的博客将要阐述的内容。本文的宗旨是从思想、设计、实现思路等方面对AQS的介绍。我们知道,AQS仅仅只是提供独占锁和共享锁两种方式,但是每种方式都有响应中断和不响应中断的区别,所以说AQS锁的更细粒度的划分为:

  • 不响应中断的独占锁(acquire)
  • 响应中断的独占锁(acquireInterruptibly)
  • 不响应中断的共享锁(acquireShared)
  • 响应中断的共享锁(acquireSharedInterruptibly)

这四种方式在AQS中的入口在上面已经标注,而释放锁的方式只有两种,独占锁的释放与共享锁的释放。分别为:

  • 独占锁的释放(release)
  • 共享锁的释放(releaseShared)
因为AQS是基于模板模式的设计,可想而知,上面的方法,子类不应该去覆盖,因为这些方法定义了整体流程,事实上作者也阻止你去覆盖它,因为这些方法都是final的。在上面所有的方法中,都调用了与之相对应的try方法。在这里需要注意的一点是,acquire和acquireInterruptibly在AQS中调用的是同一个try方法,acquireShared和acquireSharedInterruptibly也是调用相同的try方法,并且try方法在AQS中都提供了空实现。也就是说,作者暗示着子类应该去重写这些try方法,至于如何去重写try方法,完全是子类的自由。例如: ReentrantLock是一个典型的独占锁,它提供了对try方法的实现,并且提供了两种实现方式。这两种不同的try方式,就衍生出了公平与非公平的概念。即ReentrantLock提供如下:
  • 非公平方式的不响应中断的独占锁
  • 非公平方式响应中断的独占锁
  • 公平方式不响应中断的独占锁
  • 公平方式响应中断的独占锁

这里我们只是简单的阐述,关于ReentrantLock,在之后我会专门讲解。本文介绍AQS已经结束。

3、如何更加合理的解释acquire这个动词

      在AQS中,有很多API的名字很难让人读懂。按照Java的命名规范,方法的名称应该是动词+名词,例如getUser,我们就很容易的知道这个API是获取用户信息的。而在AQS中,可没有那么简单。首先这篇文章是为了后续文章做铺垫的,所以你应该需要去读懂并尽量理解这些思想。

      所谓“锁”,在AQS中,其实质是让线程获取AQS中的state的状态值,一旦可以成功获取到状态值(读到值,并且将原值加1成功),那么该线程就有运行临界区代码的权限。如何去acquire?以什么方式去acquire?当前线程目前在哪里(换句话说,当前线程是在CLH队列之中,还是目前没有入CLH队列)?所以一个完整的方法命名与解读应该遵循如下规则:

  1. 独占锁的方法命名与解读:
       acquire*+[在哪里]+[state]
比如: 
acquire方法理解:当前线程以不响应中断的方式,在CLH队列外,请求AQS的state。
acquireInterruptibly方法理解:当前线程以响应中断的方式,在CLH队列外,请求AQS的state。
acquireQueued方法理解:当前线程以不响应中断的方式,在CLH队列里,请求AQS的state。


参考文献:

  1. 再谈AbstractQueuedSynchronizer1:独占模式
  2. 【死磕Java并发】-----J.U.C之AQS:AQS简介

阅读全文
1 0
原创粉丝点击