按照模式的应用目标大致分类:
通过 识别类设计特征 来进行判断,其类构造函数以 相同的 抽象类或者姐口味输入参数。
public BufferedInputStream(InputStream in)
比较优雅的解决构建复杂对象的麻烦,这里的“复杂”是指类似需要输入的参数组合较多,如果用构造函数,则往往需要为每一种可能的输入参数组合实现相应的构造函数。
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的
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 |
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; } }
实例化对象的顺序可以分为:
但是有些编译器为了性能的原因,可能会进行 重排序 :
这样考虑两个线程发生了以下调用:
Time | Thread A | Thread B
T1 | 检查到uniqueSingleton为空 |
T2 | 获取锁 |
T3 | 再次检查到uniqueSingleton为空 |
T4 | 为uniqueSingleton分配内存空间 |
T5 | 将uniqueSingleton指向内存空间 |
T6 | | 检查到uniqueSingleton不为空
T7 | | 访问uniqueSingleton(此时对象还未完成初始化)
T8 | 初始化uniqueSingleton |
这样子在T7时刻线程B访问到了一个初始化为完成的对象。
总结:
理论依据是对象初始化过程中隐含的初始化锁。该模式被认为是替代双边检的最佳方式。
public class Singleton { private Singleton(){} public static Singleton getSingleton(){ return Holder.singleton; } /** * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例 * 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载 */ private static class Holder { /** * 静态初始化器,由JVM来保证线程安全 */ private static Singleton singleton = new Singleton(); } }