4. 单例模式(Singleton Pattern)
单例模式顾名思义,就是实现了单例,保证了在一个应用中只存在某个类的唯一实例。
单例模式包括3个变种:饿汉式,懒汉式和登记式。下边来一一的说明
(1) 饿汉式单例
此种方式在类被加载时就已经初始化了instance,所以称之为饿汉式。
但是,这种方式有个弱点,就是构造函数是私有的,也就是说这个类是无法继承的。
(2) 懒汉式单例
这个方式只在需要实例化的时候才去真正的实例化instance变量,在资源使用的效率上是比饿汉式要高的,但是它的构造函数同样是私有的,也无法继承此类。
另外,还有一个需要说明的地方,#getInstance()方法有很多人是推荐使用"双重检查锁定(DCL, double check locking)"这种方式去实现的,这样就不需要对整个方法来"synchronized"了,节省系统的开销。但是,由于Java的内存模型的原因,会导致"无序写入"问题,进而使DCL失效。但是,对于双重检查锁定,也是有补救的方法的,《Head First Design Pattern》中就提到了使用"volatile"关键字的方法,不过这个方法要在JDK1.5以上才生效,具体的代码如下:
(3) 登记式单例
登记式单例克服了饿汉式和懒汉式单例都无法继承的缺点,实现的代码如下所示:
这样,RegisterSingleton的子类就可以实现单例了,代码如下:
这里,您可能会注意到,SubRegisterSingleton的构造函数是public的,因为在SubRegisterSingleton的父类RegisterSingleton需要实例化一个SubRegisterSingleton的对象,所以,这个构造函数必须是public的,但是,这里也会出现客户端直接使用这个构造函数去构造对象的问题,这个是登记式单例的一个弱点。另外,子类的单例实现是一定要父类来参与的,这个也有点浪费资源。
单例模式顾名思义,就是实现了单例,保证了在一个应用中只存在某个类的唯一实例。
单例模式包括3个变种:饿汉式,懒汉式和登记式。下边来一一的说明
(1) 饿汉式单例
- public class EagerSingleton {
- // 单例
- private static final EagerSingleton instance = new EagerSingleton();
- private EagerSingleton() {
- // 构造函数设成private保证不能通过它去实例化
- }
- public static EagerSingleton getInstance() {
- return instance;
- }
- }
public class EagerSingleton {
// 单例
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {
// 构造函数设成private保证不能通过它去实例化
}
public static EagerSingleton getInstance() {
return instance;
}
}
此种方式在类被加载时就已经初始化了instance,所以称之为饿汉式。
但是,这种方式有个弱点,就是构造函数是私有的,也就是说这个类是无法继承的。
(2) 懒汉式单例
- public class LazySingleton {
- private static LazySingleton instance = null;
- private LazySingleton() {
- }
- public synchronized static LazySingleton getInstance() {
- if (instance == null) {
- instance = new LazySingleton();
- }
- return instance;
- }
- }
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {
}
public synchronized static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
这个方式只在需要实例化的时候才去真正的实例化instance变量,在资源使用的效率上是比饿汉式要高的,但是它的构造函数同样是私有的,也无法继承此类。
另外,还有一个需要说明的地方,#getInstance()方法有很多人是推荐使用"双重检查锁定(DCL, double check locking)"这种方式去实现的,这样就不需要对整个方法来"synchronized"了,节省系统的开销。但是,由于Java的内存模型的原因,会导致"无序写入"问题,进而使DCL失效。但是,对于双重检查锁定,也是有补救的方法的,《Head First Design Pattern》中就提到了使用"volatile"关键字的方法,不过这个方法要在JDK1.5以上才生效,具体的代码如下:
- public class LazySingleton {
- // 使用"volatile"
- private static volatile LazySingleton instance = null;
- private LazySingleton() {
- }
- // 使用DCL
- public static LazySingleton getInstance() {
- if (instance == null) {
- synchronized (LazySingleton.class) {
- if (instance == null) {
- instance = new LazySingleton();
- }
- }
- }
- return instance;
- }
- }
public class LazySingleton {
// 使用"volatile"
private static volatile LazySingleton instance = null;
private LazySingleton() {
}
// 使用DCL
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
(3) 登记式单例
登记式单例克服了饿汉式和懒汉式单例都无法继承的缺点,实现的代码如下所示:
- public class RegisterSingleton {
- private static Map<String, RegisterSingleton> registry = new HashMap<String, RegisterSingleton>();
- static {
- RegisterSingleton x = new RegisterSingleton();
- registry.put(x.getClass().getName(), x);
- }
- protected RegisterSingleton() {
- }
- public static RegisterSingleton getInstance(String name) {
- if (name == null) {
- name = RegisterSingleton.class.getName();
- }
- if (registry.get(name) == null) {
- try {
- registry.put(name, (RegisterSingleton) Class.forName(name).newInstance());
- } catch (Exception e) {
- System.out.println("Error happened.");
- }
- }
- return registry.get(name);
- }
- }
public class RegisterSingleton {
private static Map<String, RegisterSingleton> registry = new HashMap<String, RegisterSingleton>();
static {
RegisterSingleton x = new RegisterSingleton();
registry.put(x.getClass().getName(), x);
}
protected RegisterSingleton() {
}
public static RegisterSingleton getInstance(String name) {
if (name == null) {
name = RegisterSingleton.class.getName();
}
if (registry.get(name) == null) {
try {
registry.put(name, (RegisterSingleton) Class.forName(name).newInstance());
} catch (Exception e) {
System.out.println("Error happened.");
}
}
return registry.get(name);
}
}
这样,RegisterSingleton的子类就可以实现单例了,代码如下:
- public class SubRegisterSingleton extends RegisterSingleton {
- public SubRegisterSingleton() {
- }
- public static SubRegisterSingleton getInstance() {
- return (SubRegisterSingleton) RegisterSingleton.getInstance(SubRegisterSingleton.class.getName());
- }
- }
public class SubRegisterSingleton extends RegisterSingleton {
public SubRegisterSingleton() {
}
public static SubRegisterSingleton getInstance() {
return (SubRegisterSingleton) RegisterSingleton.getInstance(SubRegisterSingleton.class.getName());
}
}
这里,您可能会注意到,SubRegisterSingleton的构造函数是public的,因为在SubRegisterSingleton的父类RegisterSingleton需要实例化一个SubRegisterSingleton的对象,所以,这个构造函数必须是public的,但是,这里也会出现客户端直接使用这个构造函数去构造对象的问题,这个是登记式单例的一个弱点。另外,子类的单例实现是一定要父类来参与的,这个也有点浪费资源。