在实际应用中,线程池、缓存、日志对象、对话框对象常被设计成单例,总之,选择单例模式就是为了避免不一致状态。
下面实现几种单例模式
1.
private static SingletonDemo instance = new SingletonDemo();
private SingletonDemo(){};
public SingletonDemo getInstance()
{
return instance;
}
这种单例模式是饿汉模式,类加载的时候就会进行单例初始化。
2.
private volatile static SingletonDemo2 instance;
private SingletonDemo2(){};
public SingletonDemo2 getInstance()
{
if (instance == null)
{
synchronized (SingletonDemo2.class)
{
if (instance == null)
{
instance = new SingletonDemo2();
}
}
}
return instance;
}
这种考虑是线程安全与懒加载。需要注意一定要用 volatile 修饰单例变量,因为 instance = new SingletonDemo2();不是原子性的,这段话在JVM中会做三步完成
1.给instance分配内存
2.调用构造函数进行初始化
3.将instance对象指向分配的内存
问题在于JVM会有指令重排序,也就是说上述的 123 可能的执行结果为1-2-3 也可能是 1-3-2, 3执行完instance就不为null了,如果执行顺序是 1-3-2,实例化之前instance就已经是 非null 了,这时候其他线程进来判断 instance非空就直接返回了,就会造成问题。所以用volatile禁止指令重排序。
3.
private SingletonDemo3(){};
static class SingletonDemoInner
{
private static SingletonDemo3 INSTANCE = new SingletonDemo3();
}
public static SingletonDemo3 getInstance()
{
return SingletonDemoInner.INSTANCE;
}
这种利用了静态内部类进行懒加载。
以上三种有一个共同的缺点就是如果继承序列化类的时候需要重写readResolve方法,因为readResolve方法会调用类的默认构造函数实例化一个新的实例,这样就破坏了单例模式。还有就是反射不安全,这里不介绍了。
4.
public enum SingletonEnum
{
INSTANCE;
public void sunSomething()
{
System.out.println("Sun!");
}
}
枚举类实现单例模式可以解决序列化问题也是线程安全的,但也是饿汉模式的。