Head First 设计模式 —— 05. 单例模式

全局变量的缺点

如果将对象赋值给一个全局变量,那么必须在程序一开始就创建好对象 P170

和 JVM 实现有关,有些 JVM 的实现是:在用到的时候才创建对象

思考题

Choc-O-Holic 公司使用如下工业强度巧克力锅炉控制器

public class ChocolateBoiler { private boolean empty; private boolean boiled; public ChocolateBoiler() { empty = true; boiled = false; } public void fill() { if (isEmpty()) { empty = false; boiled = false; // 在锅炉内填满巧克力和牛奶的混合物 } } public void drain() { if (!isEmpty() && isBoiled()) { // 排出煮沸的巧克力和牛奶 empty = true; } } public void boil() { if (!isEmpty() && ! isBoiled()) { // 将炉内物煮沸 boiled = true; } } public boolean isEmpty() { return empty; } public boolnea isBoiled() { return boiled; } } 思考题

Choc-O-Holic 公司在有意识地防止不好的事情发生,你不这么认为吗?你可能会担心,如果同时存在两个 ChocolateBoiler(巧克力锅炉)实例,可能将会发生很糟糕的事情。
万一同时有多于一个的 ChocolateBoiler(巧克力锅炉)实例存在,可能发生哪些很糟糕的事呢? P176

由于只有一个物理世界的锅炉,所以如果存在多个实例时,不同实例内的变量可能与物理世界的锅炉情况不对应,造从而成错误的操作。

多线程初始化了两个实例 a 和 b,a 先成功进行 fill() 操作,此时 b 也准备进行 fill() 操作,但由于 b 内的变量没有与物理世界的锅炉情况对应,所以 b 也可以进行 fill() 操作,导致了原料溢出。

刚开始怎么也想不到会出现什么问题,看了后面单例模式多线程的问题后,仔细思考了一下,只能想到上述可能性。当然,书中忽略了只有一个实例时也存在多线程并发错误的问题(一定程度导致难以想到上述可能性)。

单例模式

确保一个类只有一个实例,并提供一个全局访问点。 P177

单例模式

Java 1.2 之前,垃圾收集器有个 bug,单例没有全局的引用时会被当作垃圾清楚。Java 1.2 及以后不存在上述问题。 P184

思考题

所有变量和方法都定义为静态的,直接把类当作一个单例,这样如何? P184

静态初始化的控制权在 Java 手上,这样做可能导致混乱,特别是当有许多类牵涉其中时。

思考题

多个类加载器有机会创建各自的单例实例,如何避免? P184

自行指定类加载器,并指定同一个类加载器。

单例模式的七种方法

推荐使用静态内部类和枚举方式

饿汉式 P181 public class Singleton { private final static Singleton INSTANCE = new Singleton(); private Singleton() {} public static Singleton getInstance() { return INSTANCE; } }

特点

线程安全

依赖 JVM 类加载机制:JVM 在加载这个类时会马上创建唯一的单例实例 P181

缺点

与全局变量一样:必须在程序一开始就实例化,没有懒加载 P170

饿汉式(变种) public class Singleton { private static Singleton INSTANCE; static { INSTANCE = new Singleton(); } private Singleton() {} public static Singleton getInstance() { return INSTANCE; } }

【扩展】 静态代码块初始化静态变量最好放在定义变量之后,否则会在执行定义变量可能出现被覆盖的问题(如果定义有赋值(包括 null),则会覆盖静态代码块已赋的值)。
原因:静态域的初始化和静态代码块的执行会从上到下依次执行。
如下写法最终会得到 null

public class Singleton { static { INSTANCE = new Singleton(); } private static Singleton INSTANCE = null; private Singleton() {} public static Singleton getInstance() { return INSTANCE; } } 懒汉式 P176 public class Singleton { private static Singleton INSTANCE = null; private Singleton() {} public static Singleton getInstance() { if (INSTANCE == null) { INSTANCE = new Singleton(); } return INSTANCE; } }

特点

使用时再实例化

缺点

线程不安全

懒汉式(变种) P180 public class Singleton { private static Singleton INSTANCE = null; private Singleton() {} public static synchronized Singleton getInstance() { if (INSTANCE == null) { INSTANCE = new Singleton(); } return INSTANCE; } }

特点

线程安全

使用时再实例化

缺点

效率低

双重校验锁 P182 public class Singleton { private volatile static Singleton INSTANCE = null; private Singleton() {} public static Singleton getInstance() { if (INSTANCE == null) { synchronized (Singleton.class) { if (INSTANCE == null) { INSTANCE = new Singleton(); } } } return INSTANCE; } }

特点

线程安全

使用时再实例化

效率较高

volatile 关键字确保:当 INSTANCE 变量杯初始化成 Singleton 实例时,多个线程能正确地处理 INSTANCE 变量 P182

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygpzx.html