单例实现
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
1.静态内部类
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
2.饿汉模式
通过定义静态的成员变量,以保证单例对象可以在类初始化的过程中被实例化。
这其实是利用了ClassLoader的线程安全机制。ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。
所以, 除非被重写,这个方法默认在整个装载过程中都是线程安全的。所以在类加载过程中对象的创建也是线程安全的。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
3.懒汉模式
双重校验锁方式实现单例。
为什么用volatile修饰?
因为编译器有可能进行指令重排优化,使得singleton对象再未完成初始化之前就对其进行了赋值,这样其他人拿到的对象就可能是个残缺的对象了。使用volatile的目的是避免指令重排。保证先进性初始化,然后进行赋值
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
4.枚举实现
枚举底层依赖Enum类实现的,这个类的成员变量都是static类型的,并且在静态代码块中实例化,保证线程安全。
枚举可以解决反序列化破坏单例的问题。
在枚举序列化的时候,Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。
普通的Java类的反序列化过程中,会通过反射调用类的默认构造函数来初始化对象。所以,即使单例中构造函数是私有的,也会被反射给破坏掉。由于反序列化后的对象是重新new出来的,所以这就破坏了单例。
但是,枚举的反序列化并不是通过反射实现的。所以,也就不会发生由于反序列化导致的单例破坏问题。
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
5.CAS实现
private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();
private Singleton() {}
public static Singleton getInstance() {
for (;;) {
Singleton singleton = INSTANCE.get();
if (null != singleton) {
return singleton;
}
singleton = new Singleton();
if (INSTANCE.compareAndSet(null, singleton)) {
return singleton;
}
}
}
单例破坏
反射破坏
private void distoryByReflect() {
Singleton singleton = Singleton.getSingleton();
try {
Class<Singleton> singleClass = (Class<Singleton>)Class.forName("com.esparks.pandora.learning.designmode.Singleton");
Constructor<Singleton> constructor = singleClass.getDeclaredConstructor(null);
constructor.setAccessible(true);
Singleton singletonByReflect = constructor.newInstance();
System.out.println("singleton : " + singleton);
System.out.println("singletonByReflect : " + singletonByReflect);
System.out.println("singleton == singletonByReflect : " + (singleton == singletonByReflect));
} catch (Exception e) {
e.printStackTrace();
}
}
解决方式:在Singleton的构造函数中增加如下代码
private Singleton() {
if (singleton != null) {
throw new RuntimeException("Singleton constructor is called... ");
}
}
序列化破坏
通过先序列化再反序列化的方式,可获取到一个新的单例对象,这就破坏了单例。
private void distoryBySerialize() {
Singleton singleton = Singleton.getSingleton();
//Write Obj to file
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(singleton);
//Read Obj from file
File file = new File("tempFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton singletonBySerialize = (Singleton)ois.readObject();
//判断是否是同一个对象
System.out.println("singleton : " + singleton);
System.out.println("singletonBySerialize : " + singletonBySerialize);
System.out.println("singleton == singletonBySerialize : " + (singleton == singletonBySerialize));
} catch (Exception e) {
e.printStackTrace();
}
}
解决方式:在Singleton中增加readResolve方法,并在该方法中指定要返回的对象的生成策略几可以了。即序列化在Singleton类中增加以下代码即可:
private Object readResolve() {
return getSingleton();
}