• 通过 JDK 源码学习灵活应用设计模式(下)


    如果大家觉得文章有错误内容,欢迎留言或者私信讨论~

      书接上回,继续延续这个话题,再重点讲一下模板、观察者两个模式在 JDK 中的应用。

    模板模式在 Collections 类中的应用

      设计模式中策略、模板、职责链三个模式常用在框架的设计中,提供框架的拓展点,让框架的使用者,在不修改框架源码的情况下,基于拓展点定制化框架的功能。 JDK 源码中的 Collections 的 sort() 方法就是利用了模板模式的这个拓展特性。
      首先,来看一下 sort() 方法是如何使用的?

    public class Demo {
    	public static void main(String[] args) {
    		List<Student> students = new ArrayList<>();
    		students.add(new Student("Alice", 19, 89.0f));
    		students.add(new Student("Peter", 20, 78.0f));
    		students.add(new Student("Leo", 18, 99.0f));
    		Collections.sort(students, new AgeAscComparator());
    		print(students);
    		Collections.sort(students, new NameAscComparator());
    		print(students);
    		Collections.sort(students, new ScoreDescComparator());
    		print(students);
    	}
    
    	public static void print(List<Student> students) {
    		for (Student s : students) {
    			System.out.println(s.getName() + " " + s.getAge() + " " + s.getScore());
    		}
    	}
    
    	public static class AgeAscComparator implements Comparator<Student> {
    		@Override
    		public int compare(Student o1, Student o2) {
    			return o1.getAge() - o2.getAge();
    		}
    	}
    	
    	public static class NameAscComparator implements Comparator<Student> {
    		@Override
    		public int compare(Student o1, Student o2) {
    			return o1.getName().compareTo(o2.getName());
    		}
    	}
    	
    	public static class ScoreDescComparator implements Comparator<Student> {
    		@Override
    		public int compare(Student o1, Student o2) {
    			if (Math.abs(o1.getScore() - o2.getScore()) < 0.001) {
    				return 0;
    			} else if (o1.getScore() < o2.getScore()) {
    				return 1;
    			} else {
    				return -1;
    			}
    		}
    	}
    }
    

      Collections.sort() 实现了对集合的排序。为了扩展性,它将其中“比较大小”这部分逻辑,委派给用户来实现。如果我们把比较大小这一部分的逻辑看作整个排序的其中一个步骤,那么我们就可以把它看作模板模式。而在代码实现方面则看起来像是基于 Callback 回调机制实现的。

      不过在其他资源中,还有人提到这是一种策略模式,如果把比较大小不是作为一个步骤,而是看作各类算法或者策略,那么确实可以将它视为一种策略模式的应用。

    观察者模式在 JDK 中的应用

      除了之前文章提到的 Guava 的 EventBus ,JDK 也提供了观察者模式的简单框架实现。在平时的开发中,如果我们不希望引入 Google Guava 开发库,可以直接使用 Java 语言本身提供的这个框架类。

      不过,它比 EventBus 要简单多了,只包含两个类:java.util.Observable 和 java.util.Observer。前者是被观察者,后者是观察者。它们的代码实现也非常简单,为了方便查看,直接 copy-paste 到了这里:

    public interface Observer {
    	void update(Observable o, Object arg);
    }
    
    public class Observable {
    	private boolean changed = false;
    	private Vector<Observer> obs;
    
    	public Observable() {
    		obs = new Vector<>();
    	}
    
    	public synchronized void addObserver(Observer o) {
    		if (o == null)
    			throw new NullPointerException();
    		if (!obs.contains(o)) {
    			obs.addElement(o);
    		}
    	}
    
    	public synchronized void deleteObserver(Observer o) {
    		obs.removeElement(o);
    	} 
    	
    	public void notifyObservers() {
    		notifyObservers(null);
    	}
    
    	public void notifyObservers(Object arg) {
    		Object[] arrLocal;
    		synchronized (this) {
    			if (!changed)
    				return;
    			arrLocal = obs.toArray();
    			clearChanged();
    		} 
    		for (int i = arrLocal.length-1; i>=0; i--)
    			((Observer)arrLocal[i]).update(this, arg);
    	}
    
    	public synchronized void deleteObservers() {
    		obs.removeAllElements();
    	} 
    	
    	protected synchronized void setChanged() {
    		changed = true;
    	}
    	 
    	protected synchronized void clearChanged() {
    		changed = false;
    	}
    }
    

      对于 Observable、Observer 的代码实现,大部分都很好理解,我们重点来看其中的两个地方。一个是 changed 成员变量,另一个是 notifyObservers() 函数。

    • 我们先来看 changed 成员变量。

      它用来表明被观察者(Observable)有没有状态更新。当有状态更新时,我们需要手动调用 setChanged() 函数,将 changed 变量设置为 true,这样才能在调用notifyObservers() 函数的时候,真正触发观察者(Observer)执行 update() 函数。否则,即便你调用了 notifyObservers() 函数,观察者的 update() 函数也不会被执行。也就是说,当通知观察者被观察者状态更新的时候,我们需要依次调用 setChanged() 和 notifyObservers() 两个函数,单独调用 notifyObservers() 函数是不起作用的。

    • 我们再来看 notifyObservers() 函数

      被观察者(Observable)里面的大部分方法都加了 synchronized 锁,但是 notifyObservers() 函数没有,之所以没有像其他函数那样,一把大锁加在整个函数上,主要还是出于性能的考虑。
       notifyObservers() 函数依次执行每个观察者的 update() 函数,每个 update() 函数执行的逻辑提前未知,有可能会很耗时。如果在 notifyObservers() 函数上加 synchronized 锁,notifyObservers() 函数持有锁的时间就有可能会很长,这就会导致其他线程迟迟获取不到锁,影响整个 Observable 类的并发性能。

      在 notifyObservers() 函数中,我们先拷贝一份观察者列表,赋值给函数的局部变量,我们知道,局部变量是线程私有的,并不在线程间共享。这个拷贝出来的线程私有的观察者列表就相当于一个快照。我们遍历快照,逐一执行每个观察者的 update() 函数。而这个遍历执行的过程是在快照这个局部变量上操作的,不存在线程安全问题,不需要加锁。所以,我们只需要对拷贝创建快照的过程加锁,加锁的范围减少了很多,并发性能提高了。

      这是一种折中的方案,这种加锁方法实际上是存在一些问题的。在创建好快照之后,添加、删除观察者都不会更新快照,新加入的观察者就不会被通知到,新删除的观察者仍然会被通知到。这种权衡是否能接受完全看你的业务场景。实际上,这种处理方式也是多线程编程中减小锁粒度、提高并发性能的常用方法。

    单例模式在 JDK 中的应用

      JDK 中 java.lang.Runtime 类就是一个单例类。这个类你有没有比较眼熟呢?是的,我们之前讲到 Callback 回调的时候,添加 shutdown hook 就是通过这个类来实现的。

      每个 Java 应用在运行时会启动一个 JVM 进程,每个 JVM 进程都只对应一个 Runtime 实例,用于查看 JVM 状态以及控制 JVM 行为。进程内唯一,所以比较适合设计为单例。在编程的时候,我们不能自己去实例化一个 Runtime 对象,只能通过 getRuntime() 静态方法来获得。

      Runtime 类的的代码实现如下所示。这里面只包含部分相关代码,其他代码做了省略。从代码中,我们也可以看出,它使用了最简单的饿汉式的单例实现方式。

    public class Runtime {
    	private static Runtime currentRuntime = new Runtime();
    	public static Runtime getRuntime() {
    		return currentRuntime;
    	} 
    	/** Don't let anyone else instantiate this class */
    	private Runtime() {}
    	//....
    	public void addShutdownHook(Thread hook) {
    		SecurityManager sm = System.getSecurityManager();
    		if (sm != null) {
    			sm.checkPermission(new RuntimePermission("shutdownHooks"));
    		}
    		ApplicationShutdownHooks.add(hook);
    	}
    	//...
    }
    

    总结

      一路跟随下来,JDK 中用到的几个经典设计模式,其中重点剖析的有:工厂模式、建造者模式、装饰器模式、适配器模式、模板模式、观察者模式,除此之外,我们还汇总了其他模式在 JDK 中的应用,比如:单例模式、享元模式、职责链模式、迭代器模式。

  • 相关阅读:
    20.添加HTTP模块
    SpringCloud-10-Eureka:注册服务提供者服务
    每周一算法:双向深搜
    stack-es-标准篇-ElasticsearchClient-match
    CP Autosar——EthIf配置
    【金九银十】343道Java面试真题整理,将每道经典题详汇
    【C++】类和对象 从入门到超神
    猿创征文|一篇打通架构设计,Java设计模式10,建造者模式
    虚幻引擎 UE5 增强输入系统
    Windows自动化重启python脚本
  • 原文地址:https://blog.csdn.net/qq_43654226/article/details/126962062