设计模式——模板模式(基于ReentrantLock源码分析)

一、什么是模板模式

1、概念

模板模式(Template Pattern)定义一个操作中算法的骨架,而将一些具体步骤延迟到子类中。它的子类可以根据需要重写抽象类类中的方法,但调用将以抽象类中定义的方式进行。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。这种类型的设计模式属于行为型模式

2、设计思路

问题:一些方法通用,却需要在每一个子类都重新写了这一方法。

解决方法:将这些通用算法抽象出来,关键代码以及代码执行逻辑在抽象类中实现,而具体步骤交给子类实现。

3、优点

(1)封装不变部分,扩展可变部分。

(2)提取公共代码,便于维护。

(3)行为由父类控制,具体实现由子类完成。

4、缺点

每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

5、使用场景

(1)有多个子类共有的方法,且逻辑相同。

(2)重要的、复杂的方法,可以考虑作为模板方法。

二、源码分析

1、类结构

 

图片来源:参考链接

 

AbstractQueueSynchronizer(AQS),抽象队列同步器,是Java并发中用来构建锁和其他同步组件的基础模板。

自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

 

 2、代码分析

import java.io.Serializable;
import java.util.concurrent.locks.Lock;

public class ReentrantLock implements Lock, Serializable {
    private static final long serialVersionUID = 7373984872572414699L;

    private final Sync sync;

    //Sync继承自模板类AQS
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;
        
        //保持抽象
        abstract void lock();

        //实现非公平锁请求资源的具体步骤
        final boolean nonfairTryAcquire(int acquires) {
            //具体实现……
        }
        
        //释放资源的具体步骤(公平锁、非公平锁通用)
        protected final boolean tryRelease(int releases) {
            //具体实现
            //……
        }
        
         //判断当前线程是否成功占用资源
        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
        //……
    }

  
     //继承自Sync实现非公平锁
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        
        //资源上锁具体操作。调用了AQS中的模板方法acquire().
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        
        //tryAcquire是AQS中定义的钩子方法。这里实现具体操作(调用Sync中实现非公平锁获取资源的方法)
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    
    //继承自Sync实现公平锁
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        //实现公平锁请求资源的具体步骤
        protected final boolean tryAcquire(int acquires) {
            //具体实现
            //……
        }
    }

    //ReentrantLock默认实现非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    //ReentrantLock(trye)表示实现非公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    //……
}

3、模板模式中的方法

(1)模板方法:一个模板方法是定义在抽象类中的,把基本操作方法组合在一起形成一个总算法或一个总行为的方法。一个抽象类可以有任意多个模板方法,而不限于一个。 每一个模板方法都可以调用任意多个具体方法。(一般使用public final修饰)

(2)基本方法:基本方法又可以分为三种:抽象方法(Abstract Method)、具体方法(Concrete Method)和钩子方法(Hook Method)。

  • 抽象方法:一个抽象方法由抽象类声明,由具体子类实现。(使用abstract修饰
  • 具体方法:一个具体方法由抽象类声明并实现,而子类并不实现或置换。(一般使用private修饰)
  • 钩子方法:一个钩子方法由抽象类声明并实现,而子类会加以扩展。通常抽象类给出的实现是一个空实现,作为方法的默认实现。

4、什么是钩子方法?

如上所言,钩子方法是抽象类中方法体为空的方法。比如AQS中tryAcquire()和traRelease()就是两个钩子方法。

protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();//只抛出异常并没有实际操作
    }
protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

ReentranLock中的内部静态抽象类Sync中继承AQS具体实现了tryRelase(),内部静态类FairSync和NonfairSync继承Sync分别具体实现了traAcquire()。参考上述ReentranLock类的伪代码。

AQS中的钩子方法:

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

小结

1、模板模式定义一个操作中算法的骨架,而将一些具体步骤延迟到子类中。

2、模板方法体现模板模式的通用性,描述了代码执行的逻辑步骤,是不可变动的。模板方法通过调用包括钩子方法在内的基本方法来实现功能。

3、钩子方法就是需要子类具体现实的方法,体现模板模式的扩展性。

参考链接

1、基于ReentrantLock理解AQS同步队列的细节和设计模式

posted @ 2021-10-08 17:20  云墨亦白  阅读(270)  评论(0)    收藏  举报