一:为什么使用
对象频繁创建、销毁对系统性能和内存来说不是一个好事情,特别是及其消耗资源的大对象
或是对象实例化需要进行I/O操作
。使用单例模式在内存中确保对象唯一
,可以很好解决上述问题
二:饿汉式单例
public class Singleton {
private Singleton(){}
private static Singleton sf = new Singleton();
public static Singleton getInstance(){
return sf;
}
}
饿汉式特点:
- 私有构造函数阻止new实例化对象操作确保对象唯一
- 提供静态方法返回对象
饿汉式问题:
- 静态属性sf会跟随类加载完成对象实例初始化,但是如果这个对象暂时是不需要的,是否可以延时加载即使用再初始化
三:懒汉式单例
public class Singleton {
private Singleton(){}
private static Singleton sf = null;
public static Singleton getInstance(){
if (sf == null)
return sf = new Singleton();
return sf;
}
}
懒汉式特点:
- 通过在方法中判断、创建对象的方式实现了延时加载的目标
懒汉式问题:
- if条件判断的使用导致并发场景下对象不唯一情况产生,即线程A通过判定在初始化之前线程B获得资源完成初始化,这时候A再获取资源进行初始化操作
四:双重锁校验
直接在getInstance()上加synchronized关键字确实可以解决线程安全问题,但是锁是及其消耗资源的,为了性能最优可以减少锁资源争夺从而提出双重锁校验
public class Singleton {
private Singleton(){}
private static volatile Singleton sf = null;
private static final Object o = new Object();
public static Singleton getInstance(){
if (sf == null) {
synchronized (o) {
if (sf == null)
return sf = new Singleton();
}
}
return sf;
}
}
务必注意sf对象使用关键字volatile
修饰保证线程间数据透明,线程可以及时获取到sf对象的变化信息
五:内部类实现
双重锁校验在一定程度上优化了并发线程对于锁资源的争夺,但是还是无法完全避免。利用内部类实现则可以从根上改变使用锁解决线程安全问题
public class Singleton {
private Singleton(){}
private static class InnerClass{
private static Singleton sf = new Singleton();
}
public static Singleton getInstance(){
return InnerClass.sf;
}
}
六:序列化、反射安全
上面四个单例模式方案逐步递进解决有关延迟加载与线程安全等,但是都共同存在反序列化破坏单例规则即反序列化产生不唯一对象
的问题
public class Singleton implements Serializable{
private Singleton(){}
private static Singleton s = new Singleton();
public static Singleton getInstance(){
return s;
}
public static void write() throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.txt"));
oos.writeObject(Singleton.getInstance());
oos.flush();
oos.close();
}
public static Singleton get() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.txt"));
Singleton s = (Singleton)ois.readObject();
return s;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
write();
Singleton singleton = get();
System.out.println(singleton == s); // false
}
}
解决序列化带来的对象不唯一问题比较简单粗暴,如下所示声明方法readResolve()
即可:
private Object readResolve(){
return s;
}
反射对单例模式的破坏同样很直接,针对反射问题可以对构造函数进行如下处理。对单例模式的类使用反射本身就是在犯罪,虽然如下处理可以防止,但是要铁了心修改flag的值这也是无法防范
private static boolean flag = true;
private Singleton(){
if(flag)
flag = false;
else
throw new RuntimeException("this Object already exists!");
}