目录
模板方法模式应用于AQS机制
实际开发中应用场景还有哪里用到了模板方法
策略模式在JDK源码中的应用
策略模式
策略模式的优缺点及应用场景
策略模式在Comparator接口中的应用
策略模式在 JDK 中的应用
模板设计模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
AQS的典型设计模式就是模板方法设计模式。AQS全家桶(ReentrantLock,Semaphore)的衍生实现,就体现出这个设计模式。如AQS提供tryAcquire,tryAcquireShared等模板方法,给子类实现自定义的同步器。
AQS这个抽象类把模板方法设计模式运用地炉火纯青,它里面定义了一系列的模板方法,比如下面这些:
// 获取互斥锁
public final void acquire(int arg) {
// tryAcquire(arg)需要子类实现
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 获取互斥锁可中断
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// tryAcquire(arg)需要子类实现
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
// 获取共享锁
public final void acquireShared(int arg) {
// tryAcquireShared(arg)需要子类实现
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
// 获取共享锁可中断
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// tryAcquireShared(arg)需要子类实现
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
// 释放互斥锁
public final boolean release(int arg) {
// tryRelease(arg)需要子类实现
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// 释放共享锁
public final boolean releaseShared(int arg) {
// tryReleaseShared(arg)需要子类实现
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
获取锁、释放锁的这些方法基本上都穿插在ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch的源码解析中了,现在看他们是不是舒服多了,如果一开始就看这些源码,难免会很晕。
上面学习了AQS中几个重要的模板方法,下面再学习下几个需要子类实现的方法:
// 互斥模式下使用:尝试获取锁
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// 互斥模式下使用:尝试释放锁
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
// 共享模式下使用:尝试获取锁
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
// 共享模式下使用:尝试释放锁
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
// 如果当前线程独占着锁,返回true
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
这几个方法为什么不直接定义成抽象方法呢?
因为子类只要实现这几个方法中的一部分就可以实现一个同步器了,所以不需要定义成抽象方法。
策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性,现在我们来分析其基本结构和实现方法。
实际场景的案例:假如现在需要做一份虾。每个人的口味不同,可能去吃饭时候点的做法也不同。这里我们吧厨师的对虾的烹饪方式,作为策略的抽象。不同的做法为抽象策略的具体实现(不同算法)。
//抽象策略
public interface AbstractShrimpCook {
// 大虾烹饪方式
void cook();
}
public class BraiseShrimp implements AbstractShrimpCook{
@Override
public void cook() {
System.out.println("红烧大虾...");
}
}
public class PoachShrimp implements AbstractShrimpCook {
@Override
public void cook() {
System.out.println("水煮大虾...");
}
}
//清蒸
public class SteamedShrimp implements AbstractShrimpCook {
public void cook() {
System.out.println("清蒸大虾...");
}
}
public class Context {
AbstractShrimpCook abstractShrimpCook;
public AbstractShrimpCook getAbstractShrimpCook() {
return abstractShrimpCook;
}
public void setAbstractShrimpCook(AbstractShrimpCook abstractShrimpCook) {
this.abstractShrimpCook = abstractShrimpCook;
}
// 调用
public void cook() {
abstractShrimpCook.cook();
}
}
public class Client {
public static void main(String[] args) throws Exception {
// 水煮
String cookType = "POACH";
AbstractShrimpCook abstractShrimpCook;
if ("POACH".equals(cookType)) {
abstractShrimpCook = new PoachShrimp();
}
else if ("BRAISE".equals(cookType)) {
abstractShrimpCook = new BraiseShrimp();
}
else if ("STEAMED".equals(cookType)) {
abstractShrimpCook = new SteamedShrimp();
} else {
throw new Exception("未找到指定类型策略");
}
Context context = new Context();
context.setAbstractShrimpCook(abstractShrimpCook);
context.cook();
}
}
Comparator接口提供比较方法compare()抽象,实现类可以自定义的传比较算法,用于排序。
Comparator体现了一种策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。
java.util.Comparator 就是抽象策略类:
@FunctionalInterface
public interface Comparator {
int compare(T o1, T o2);
}
java.util.Arrays 是环境类:
Comparable与Comparator的区别 - 掘金
在我们创建线程池时,会调用ThreadPoolExecutor的构造函数 new 一个对象,在构造函数中需要传入七个参数,其中有一个参数叫 RejectedExecutionHandler handler 也就是线程的拒绝策略。
传入拒绝策略之后将对象赋给 ThreadPoolExecutor 对象的成员变量 handler,在需要对加入线程池的线程进行拒绝时,直接调用 RejectedExecutionHandler 中的 reject 方法即可,方法内部调用传入 handler 的 rejectedExecution 方法。
但是 RejectedExecutionHandler 是一个接口,也就是说我们需要传入具体的实现,这里便是使用的策略模式。RejectedExecutionHandler 接口对应抽象策略类,下面四种实现类对应具体策略;RejectedExecutionHandler 对应环境类,外部调用 RejectedExecutionHandler 的 reject 方法,再由 RejectedExecutionHandler 内部调用具体策略实现的方法。
RejectedExecutionHandler接口
使用调用者的线程来处理
中止策略
ThreadPoolExecutor中默认的拒绝策略就是AbortPolicy直接抛出异常
丢弃策略
弃老策略
虽然线程池有默认的拒绝策略,但实际开发过程中,有些业务场景,直接拒绝的策略往往并不适用,有时候可能会选择舍弃最早开始执行而未完成的任务、也可能会选择舍弃刚开始执行而未完成的任务等更贴近业务需要的策略。所以,为线程池配置其他拒绝策略或自定义拒绝策略是很常见的需求,这就需要自定义拒绝策略使用JDK中定义好的接口,这里就有策略模式的思想。
executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 拒绝策略的逻辑
}
});
死磕 java同步系列之AQS终篇(面试) - 掘金
【深入设计模式】策略模式—策略模式详解及策略模式在源码中的应用 - 掘金
设计模式-策略模式及应用 - 掘金----JDK源码中的应用
Spring Boot中如何配置线程池拒绝策略,妥善处理好溢出的任务 - 掘金