在学完volatile和CAS之后,近几天在撸AbstractQueuedSynchronizer(AQS)的源代码,很多并发工具都是基于AQS来实现的,这也是并发专家Doug Lea的初衷,通过写一个这样的基础工具来提高j.u.c的灵活性。具体可以看这篇论文的一段原文,我摘录一下:
As is well-known (see e.g., [2]) nearly any synchronizer can be used to implement nearly any other. For example, it is possible to build semaphores from reentrant locks, and vice versa. However,doing so often entails enough complexity, overhead, and inflexibility to be at best a second-rate engineering option.Further, it is conceptually unattractive. If none of these constructs are intrinsically more primitive than the others, developers should not be compelled to arbitrarily choose one of them as a basis for building others. Instead, JSR166 establishes a small framework centered on class AbstractQueuedSynchronizer,that provides common mechanics that are used by most of the provided synchronizers in the package, as well as other classes that users may define themselves.
翻译下来大致的意思就是,如果使用信号量、锁等互为彼此实现,这样一种设计只会让问题变得复杂而且缺乏灵活性,既然这样,JSR166提出了AQS来作为基础工具来对外提供一种通用的机制。
AQS的设计和实现非常复杂,所以我打算通过DEBUG的方式看下内部是如何实现的,在看源码的过程中顺便学习了一个设计模式-模板方法。
1、模板方法结构
模板方法的本质其实就是类的继承,模板方法模式需要开发抽象类和具体子类的设计之间的协作,我们实际工作中应该有很多这样的场景,比如我们通常会在业务逻辑层定义好Service类的抽象方法,而实际的业务逻辑实现会交给ServiceImpl类。模板方法模式的类结构图大致如下:
图一
抽象模板(AbstractTemplate)角色有如下责任:
定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤。定义并实现了一个模板方法,这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。AbstractQueuedSynchronizer就是这样一个抽象模板。
具体模板(ConcreteTemplate)角色又有如下责任:
实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。ReentrantLock算是这样一个具体模板,不过ReentrantLock把自己的实现又委托给了内部实现类Sync。
模板模式的关键是:子类可以置换掉父类的可变部分,但是子类却不可以改变模板方法所代表的顶级逻辑。每当定义一个新的子类时,不要按照控制流程的思路去想,而应当按照“责任”的思路去想。换言之,应当考虑哪些操作是必须置换掉的,哪些操作是可以置换掉的,以及哪些操作是不可以置换掉的。使用模板模式可以使这些责任变得清晰。
2、模板方法模式中的方法
模板方法中的方法可以分为两大类:模板方法和基本方法。
模板方法:一个模板方法是定义在抽象类中的,把基本操作方法组合在一起形成一个总算法或一个总行为的方法。一个抽象类可以有任意多个模板方法,而不限于一个。每一个模板方法都可以调用任意多个具体方法。
基本方法:基本方法又可以分为三种:抽象方法(Abstract Method)、具体方法(Concrete Method)和钩子方法(Hook Method)。
抽象方法:一个抽象方法由抽象类声明,由具体子类实现。在Java语言里抽象方法以abstract关键字标示。
具体方法:一个具体方法由抽象类声明并实现,而子类并不实现或置换。
钩子方法:一个钩子方法由抽象类声明并实现,而子类会加以扩展。通常抽象类给出的实现是一个空实现,作为方法的默认实现。
3、由AbstractQueuedSynchronizer和ReentrantLock来看模版方法模式
以ReentrantLock的lock方法为例来看下模板方法的体现。具体代码如下:
public class MutexDemo { // private static Mutex mutex = new Mutex(); private static ReentrantLock mutex = new ReentrantLock(); public static void main(String[] args) { for (int i = 0; i < 10; i++) { Thread thread = new Thread(() -> { mutex.lock(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } finally { mutex.unlock(); } }); thread.start(); } } }
默认是以非公平锁来初始化ReentrantLock,所以方法中ReentrantLock将自己的行为委托给NonfairSync,NonfairSync继承Sync,Sync又继承了AbstractQueuedSynchronizer。在整个过程中AbstractQueuedSynchronizer的方法acquire其实就是模板模式里面的模板方法,方法release也是一样的,这两个方法是整套框架里面的顶级逻辑。在这个顶级逻辑之外,Sync给出了lock方法,这是一个抽象方法,下设2种实现,分别是公平锁FairSync和非公平锁NonfairSync。在顶级逻辑里面还有方法的具体方法实现,比如addWaiter和acquireQueued。另外还有一个钩子方法,如tryAcquire,在AQS里面这个钩子方法是没有具体的实现的,具体的实现交给了NonfairSync。感兴趣的读者可以按照这思路跟踪下代码,然后理解一下模板方法这个设计模式。
参考资料:
https://github.com/lingjiango/ConcurrentProgramPractice
http://www.cnblogs.com/java-my-life/archive/2012/05/14/2495235.html