《单例模式》
Java的单例模式是非常常见的设计模式,在Jdk的库中也被广泛应用,这里介绍常见的饿汉模式和懒汉模式,以及如何在多线程的环境下完善懒汉模式:
饿汉单例模式
//饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton1 {
private Singleton1() {}
private static final Singleton1 single = new Singleton1();
//静态工厂方法
public static Singleton1 getInstance() {
return single;
}
}
懒汉单例模式
//懒汉式单例类.在第一次调用的时候实例化自己
public class Singleton {
private Singleton() {}
private static Singleton single=null;
//静态工厂方法
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
上面的懒汉模式在单线程的情况下没有问题,但是在多线程环境下就会出现一个问题:会创建多个singleton实例,所以我们需要将创建对象的代码进行同步。
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
使用synchronized同步方法后, 多线程的问题处理了,但是每次调用该方法或的实例的时候都会进行锁的判断,而事实上我们只需要第一次获取实例的时候进行判断即可,所以如果你的程序有性能上的要求,那么就不要使用上面的方法,在jdk1.4以后我们可以使用双重判断的方式来进行优化:
//使用volatile修饰
private static volatile Singleton singleton;
public static Singleton getInstance(){
if (singleton == null){
synchronized (Singleton.class) {
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
我们发现singleton实例被volatile修饰了,volatile在多线程环境下保证了变量的可见性和可序性,因为java中new对象实际上不是一个原子性作,它分为三步:1.分配内存 2.初始化对象 3.分配地址。当不用volatile修饰时,有可能发生这种情况:线程1执行new instance操作,由于Java的指令重排,可能导致此时直接返回了没有初始化的对象,而此时线程2进行if(singleton==null)判断,发现实例不为null,直接获取,最后导致获得的实例没有初始化,程序错误。
写到这里多线程的问题解决了,但是通过反射的暴力访问构造器依然可以创建多个实例,所以我们可以在构造器中进行判断,如果是第二次创建对象就抛出异常:
private static boolean flag = true;
private Singleton(){
synchronized (Singleton.class) {
if (flag){
flag = !flag;
}else{
throw new RuntimeException("不允许创建多个对象");
}
}
}
我们还需要解决反序列化的问题就大功告成了,Java的反序列化是从通过IO流从文件中读取数据转换成对象,单利模式要想保证对象的唯一必须添加readResolve方法,反序列化的时候会调用该方法获得对象
private Object readResolve(){
return instance; //反序列化的时候会调用该方法获得对象
}
可以看到一个单利模式我们要保证安全的情况下,代码量也不小。从Java1.5有枚举后,我们可以使用枚举实现单例模式,这种实现方式非常简单,而且由于枚举类默认实现了序列化,我们也无需为序列化和反序列化对象出现多个的情况操心:
public enum MyEnum {
INSTANCE;
private Singleton singleton;
private MyEnum() {
singleton = new Singleton();
}
public Singleton getInstance(){
return singleton;
}
}
class Singleton{
}
Singleton singleton = MyEnum.INSTANCE.getInstance();
总结
1.懒汉和饿汉的选择:饿汉模式下的实例的创建随着的加载而被创建,所以如果你的程序并不一定适用到该对象或者该对象的初始化比较耗时,就可以使用懒汉模式。
2.使用懒汉模式一定要处理多线程的问题,根据是否需要提高程序的性能从而选择使用同步方法还是双重判断。
3.双重判断下的懒汉模式在多线程环境下安全的前提条件是你的JDK版本是1.4以上,因为1.4以下的JDK对volatile并不是支持的很好。
4.引用effective java书中提到的:单元素的枚举类型已经成为实现Singleton的最好方法