常用设计模式在Java源码中的体现

目录

模板方法模式应用于AQS机制

实际开发中应用场景还有哪里用到了模板方法

策略模式在JDK源码中的应用

策略模式

策略模式的优缺点及应用场景

策略模式在Comparator接口中的应用

策略模式在 JDK 中的应用


模板方法模式应用于AQS机制

模板设计模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

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();
}

这几个方法为什么不直接定义成抽象方法呢?

因为子类只要实现这几个方法中的一部分就可以实现一个同步器了,所以不需要定义成抽象方法。

常用设计模式在Java源码中的体现_第1张图片

常用设计模式在Java源码中的体现_第2张图片

常用设计模式在Java源码中的体现_第3张图片

实际开发中应用场景还有哪里用到了模板方法

  • 其实很多框架中都有用到了模板方法模式
  • 例如:数据库访问的封装、Junit单元测试、servlet中关于doGet/doPost方法的调用等

策略模式在JDK源码中的应用

策略模式

策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性,现在我们来分析其基本结构和实现方法。

  1. 模式的结构
  • 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
  • 环境(Context)类:持有一个策略类的引用,最终给客户端调用。

常用设计模式在Java源码中的体现_第4张图片

实际场景的案例:假如现在需要做一份虾。每个人的口味不同,可能去吃饭时候点的做法也不同。这里我们吧厨师的对虾的烹饪方式,作为策略的抽象。不同的做法为抽象策略的具体实现(不同算法)。

  • 抽象策略
//抽象策略
public interface AbstractShrimpCook {
	// 大虾烹饪方式
	void cook();
}
  • 具体实现A
public class BraiseShrimp implements AbstractShrimpCook{
	@Override
	public void cook() {
		System.out.println("红烧大虾...");
	}
}
  • 具体实现B
public class PoachShrimp implements AbstractShrimpCook {
	@Override
	public void cook() {
		System.out.println("水煮大虾...");
	}
}
  • 具体实现C
//清蒸
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();
	}
}

策略模式的优缺点及应用场景

优点:

  • 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if...else 语句、switch...case 语句。
  • 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
  • 策略模式可以提供相同行为的不同实现,客户可以根据不同需求,动态切换实现。
  • 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
  • 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

缺点:

  • 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
  • 策略模式造成很多的策略类,增加维护难度。

策略模式在Comparator接口中的应用

Comparator接口提供比较方法compare()抽象,实现类可以自定义的传比较算法,用于排序。

Comparator体现了一种策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。

java.util.Comparator 就是抽象策略类:

@FunctionalInterface
public interface Comparator {    
    int compare(T o1, T o2);    
}

java.util.Arrays 是环境类:

常用设计模式在Java源码中的体现_第5张图片

Comparable与Comparator的区别 - 掘金

策略模式在 JDK 中的应用

在我们创建线程池时,会调用ThreadPoolExecutor的构造函数 new 一个对象,在构造函数中需要传入七个参数,其中有一个参数叫 RejectedExecutionHandler handler 也就是线程的拒绝策略。

常用设计模式在Java源码中的体现_第6张图片

传入拒绝策略之后将对象赋给 ThreadPoolExecutor 对象的成员变量 handler,在需要对加入线程池的线程进行拒绝时,直接调用 RejectedExecutionHandler 中的 reject 方法即可,方法内部调用传入 handler 的 rejectedExecution 方法。

常用设计模式在Java源码中的体现_第7张图片

但是 RejectedExecutionHandler 是一个接口,也就是说我们需要传入具体的实现,这里便是使用的策略模式。RejectedExecutionHandler 接口对应抽象策略类,下面四种实现类对应具体策略;RejectedExecutionHandler 对应环境类,外部调用 RejectedExecutionHandler 的 reject 方法,再由 RejectedExecutionHandler 内部调用具体策略实现的方法。

RejectedExecutionHandler接口

常用设计模式在Java源码中的体现_第8张图片

常用设计模式在Java源码中的体现_第9张图片

使用调用者的线程来处理

常用设计模式在Java源码中的体现_第10张图片

中止策略

ThreadPoolExecutor中默认的拒绝策略就是AbortPolicy直接抛出异常

常用设计模式在Java源码中的体现_第11张图片

丢弃策略

常用设计模式在Java源码中的体现_第12张图片

弃老策略

常用设计模式在Java源码中的体现_第13张图片

开发中的拒绝策略

虽然线程池有默认的拒绝策略,但实际开发过程中,有些业务场景,直接拒绝的策略往往并不适用,有时候可能会选择舍弃最早开始执行而未完成的任务、也可能会选择舍弃刚开始执行而未完成的任务等更贴近业务需要的策略。所以,为线程池配置其他拒绝策略或自定义拒绝策略是很常见的需求,这就需要自定义拒绝策略使用JDK中定义好的接口,这里就有策略模式的思想。

executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 拒绝策略的逻辑
    }
});

巨人的肩膀

死磕 java同步系列之AQS终篇(面试) - 掘金

【深入设计模式】策略模式—策略模式详解及策略模式在源码中的应用 - 掘金

设计模式-策略模式及应用 - 掘金----JDK源码中的应用

Spring Boot中如何配置线程池拒绝策略,妥善处理好溢出的任务 - 掘金

你可能感兴趣的:(java,设计模式,开发语言)