写好一个简单的单例模式真不容易啊。。
但是第一个问题是,为什么要使用这种基于static的单例模式?直接用static不可以吗?给自己搞这么麻烦。
这其实和java的对象化设计有关。因为java喜欢对象,所以只为对象做了约定能力(接口)。如果你搞一个纯static的非单例类,那就没法约定了。所以,单例模式是一种对接口化的妥协。考虑到大部分情况下一个功能不止一个实现类,所以扩展的需要,接口是不可抛弃的。
往根本上说,使用单例,是因为java喜欢对象,所以我们在任何时候都应该尽量的使用对象。
实际使用时初始化
最简单的单例版本。这里需要注意的问题是构造函数必须要有而且必须要用private修饰。否则java将自动为该类创建一个public的无参构造器。
public class SingletonDemo1 {
private static SingletonDemo1 instance;
private SingletonDemo1(){}
public static SingletonDemo1 getInstance(){
if (instance == null) {
instance = new SingletonDemo1();
}
return instance;
}
}
上述的多线程版本是
public class SingletonDemo2 {
private static SingletonDemo2 instance;//注意这里不需要加volatile。
private SingletonDemo2(){}
public static synchronized SingletonDemo2 getInstance(){
if (instance == null) {
instance = new SingletonDemo2();
}
return instance;
}
}
显然效率很低,任何一次访问都要获取锁,而实际上是没必要的。针对这种问题,我们可以进行双重检查。
public class SingletonDemo7 {
private volatile static SingletonDemo7 singletonDemo7;
private SingletonDemo7(){}
public static SingletonDemo7 getSingletonDemo7(){
if (singletonDemo7 == null) {
synchronized (SingletonDemo7.class) {
if (singletonDemo7 == null) {
singletonDemo7 = new SingletonDemo7();
}
}
}
return singletonDemo7;
}
}
这里比较恶心的是指令重排。 singletonDemo7 = new SingletonDemo7()并不是一个原子性操作。
对象的初始化完成和把singletonDemo7的值置为对象地址的顺序是不固定的!所以需要volatile来禁止指令重排。
这个方法是需要加锁的,势必影响性能。在早期的JAVA程序员里可谓声名狼藉。所以我们有下面方法。
利用类加载的初始化机制(最为推荐的方法)
利用类加载可以避开了多线程问题。
public class SingletonDemo3 {
private static SingletonDemo3 instance = new SingletonDemo3();
private SingletonDemo3(){}
public static SingletonDemo3 getInstance(){
return instance;
}
}
这里的饿汉版本如下,利用静态内部类来实现。静态内部类也是必须使用到才会被加载。
public class SingletonDemo5 {
private static class SingletonHolder{
private static final SingletonDemo5 instance = new SingletonDemo5();
}
private SingletonDemo5(){}
public static final SingletonDemo5 getInsatance(){
return SingletonHolder.instance;
}
}
枚举
最好的办法,通过枚举。
public enum EnumSingleton {
INSTANCE;
EnumSingleton(){
System.out.println("EnumSingleton constructor execute");
}
}
通过反射破坏单例
上面的单例都有一个共同的特点,就是构造函数必须是private的。但是恶意捣蛋的程序员可能会写出如下代码。
Constructor con = SingetonTest.class.getDeclaredConstructor();
con.setAccessible(true);
// 通过反射获取实例
SingetonTest singetonTest1 = (SingetonTest)con.newInstance();
防止这种行为的办法是在构造函数里同样加上检查是否是否单例为空。利用实际使用时初始化的加载还需要进行双重检查+锁。
通过反序列化破坏单例
不让我用反射?如果你的单例类的定义是implements Serializable的,emmmmmm…可以通过先序列化在反序列化创造一个单例嘛。序列化是深受安全争议的技术,请务必小心使用,同时当然也是强而有力的捣蛋技术。