谈谈你知道的设计模式? - 《java核心技术》笔记

分类

按照模式的应用目标大致分类:

  • 创建型模式:是对对象创建过程的问题和解决方案的总结,比如单例、工厂、构建器、原型。
  • 结构型模式:针对软件设计结构的总结,关注于类、对象继承、组合方式的实践经验,比如适配器、装饰者、桥接、代理、组合、外观、享元。
  • 行为型模式:从类或对象之间交互、职责划分等角度总结等模式。比如策略、解释器、命令、观察者、迭代器、模版方法、访问者。

例子

装饰器

通过识别类设计特征来进行判断,其类构造函数以相同的抽象类或者姐口味输入参数。

public BufferedInputStream(InputStream in)

77ad2dc2513da8155a3781e8291fac33

构建器

比较优雅的解决构建复杂对象的麻烦,这里的“复杂”是指类似需要输入的参数组合较多,如果用构造函数,则往往需要为每一种可能的输入参数组合实现相应的构造函数。

HttpRequest request = HttpRequest.newBuilder(new URI(uri))
                     .header(headerAlice, valueAlice)
                     .headers(headerBob, value1Bob,
                      headerCarl, valueCarl,
                      headerBob, value2Bob)
                     .GET()
                     .build();

单例

版本一:

 public class Singleton {
       private static Singleton instance = new Singleton();
       public static Singleton getInstance() {
          return instance;
       }
    }

有问题,没有把构造函数声明为private的

版本二:private化构造函数+懒加载。

public class Singleton {
        private static Singleton instance;
        private Singleton() {
        }
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
        return instance;
        }
    }

问题:多线程不可用,比如考虑以下情况:

Time Thread A Thread B
T1 检查到uniqueSingleton为空
T2 检查到uniqueSingleton为空
T3 初始化对象A
T4 返回对象A
T5 初始化对象B
T6 返回对象B

版本三:双检锁(Double Check Lock)+volatile

public class Singleton {
    private static volatile Singleton singleton = null;
    private Singleton() {
    }

    public static Singleton getSingleton() {
        if (singleton == null) { // 尽量避免重复进入同步块
            synchronized (Singleton.class) { // 同步.class,意味着对同步类方法调用
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
volatile关键字的作用:

实例化对象的顺序可以分为:

  1. 分配内存空间。
  2. 初始化对象。
  3. 将对象指向刚分配的内存空间。

但是有些编译器为了性能的原因,可能会进行重排序

  1. 分配内存空间。
  2. 将对象指向刚分配的内存空间。
  3. 初始化对象。

这样考虑两个线程发生了以下调用:

Time Thread A Thread B
T1 检查到uniqueSingleton为空
T2 获取锁
T3 再次检查到uniqueSingleton为空
T4 为uniqueSingleton分配内存空间
T5 将uniqueSingleton指向内存空间
T6 检查到uniqueSingleton不为空
T7 访问uniqueSingleton(此时对象还未完成初始化)
T8 初始化uniqueSingleton

这样子在T7时刻线程B访问到了一个初始化为完成的对象。

volatile保证重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。

总结:

  • volatile提供可见性,以保证getInstance返回的是初始化完全的对象;
  • 在加锁前检查,尽量避免进入相对昂贵的同步快;
  • 在class级别同步,保证现场安全的类方法调用。

版本四:使用静态内部类

理论依据是对象初始化过程中隐含的初始化锁。该模式被认为是替代双边检的最佳方式。

静态内部类的优点:外部类加载时不需要立即加载内部类,内部类不被加载则不去初始化singleton。

知识点
  1. clinit()和init()的区别
    :在jvm第一次加载class文件时调用,包括静态变量初始化语句和静态块的执行
    :在实例创建出来的时候调用,包括调用new操作符;调用Class或java.lang.reflect.Constructor对象的newInstance()方法;调用任何现有对象的clone()方法;通过java.io.ObjectInputStream类的getObject()方法反序列化。

()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的。

()方法与类的构造函数(或者说实例构造器中的()方法)不同, 它不需要显示的调用父类构造器,虚拟机会保证在子类的()方法执行之前。

()方法对于类或者接口来说并不是必须的,如果一个类没有静态语句块,也就没有变量的赋值操作,那么编译器可以不为这个类生成()方法。

init is the (or one of the) constructor(s) for the instance, and non-static field initialization.
clinit are the static initialization blocks for the class, and static field initialization.
上面这两句是Stack Overflow上的解析,很清楚init是instance实例构造器,对非静态变量解析初始化,而clinit是class类构造器对静态变量,静态代码块进行初始化。

class X {
   static Log log = LogFactory.getLog(); // <clinit>
   private int x = 1;   // <init>
   X(){
      // <init>
   }
   static {
      // <clinit>
   }
}
  1. clinit的线程安全性
    虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。如果在一个类的()方法中有耗时很长的操作,就可能造成多个进程阻塞(需要注意的是,其他线程虽然会被阻塞,但如果执行()方法后,其他线程唤醒之后不会再次进入()方法。同一个加载器下,一个类型只会初始化一次。),在实际应用中,这种阻塞往往是很隐蔽的。
public class Singleton {
	private Singleton(){}
	public static Singleton getSingleton(){
    	return Holder.singleton;
	}
    /**
     * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
     * 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
     */
	private static class Holder {
        /**
         * 静态初始化器,由JVM来保证线程安全
         */
    	private static Singleton singleton = new Singleton();
	}
}

问题:由于是静态内部类的形式去创建单例,所以外部无法传递参数进去。实际使用时可以在静态内部类和DCL模式中自己权衡。

comments powered by Disqus