前言:
当前你们对“单例模式的几大要素”大致比较重视,你们都想要剖析一些“单例模式的几大要素”的相关内容。那么小编在网摘上搜集了一些关于“单例模式的几大要素””的相关文章,希望我们能喜欢,咱们一起来了解一下吧!一、关于创建
单例模式是创建型模式中的一种设计模式:其实例在整个应用全局中有且只有一个,并且该实例由自身创建,不允许被克隆、反序列、反射。为了满足这些要求,一个标准的单例模式一般需包含以下6要素:
1.)拥有一个私有的静态实例;
2.)构造器私有化;
3.)对外提供获取实例的静态方法,且必须保证同步,防止多线程环境同时执行;
4.)关于克隆,如果单例类实现了Cloneable标识接口,可以通过clone()破坏单例。因此,非必要不实现Cloneable标识接口,如果需要实现clone()直接返回相关单例对象。
@Override protected Singleton clone() throws CloneNotSupportedException { return singleton; }
5.)关于序列化,如果单例类实现了Serializable接口, 就可以通过反序列化破坏单例。因此,非必要不实现Serializable序列化接口,如果需要实现序列化接口,可以重写反序列化方法readResolve(), 反序列化时直接返回相关单例对象。
public Object readResolve() throws ObjectStreamException { return singleton; }
6.)关于反射,除枚举类型的单例,尚未知晓可靠的防止反射的方法(如果有好的方法,请留言探讨)。
二、关于分类2.1 饿汉式枚举常量静态代码块静态实例2.2 懒汉式同步方法静态内部类DCL(Double Check Lock) 双重检测内部枚举类三、关于案例3.1.饿汉式
优点 :
线程安全在类加载的同时已经创建好一个静态对象到内存,调用时反应速度快
缺点 :
资源利用率不高,应用运行中可能永远用不到此单例,但这个实例仍然初始化,造成内存浪费3.1.1 枚举常量
public enum Singleton { SINGLETON; public static Singleton getInstance(){ return SINGLETON; } }3.1.2 静态代码块
public class Singleton{ private static final Singleton singleton; //静态代码块。根据实际情况可以做一些初始化操作。 static{ singleton = new Singleton(); } //构造方法私有化 public Singleton(){} //对外提供获取实例的方法 public static Singleton getSingleton() { return singleton; } }3.1.3 静态实例
public class Singleton{ private static final Singleton singleton = new Singleton(); //构造方法私有化 private Singleton(){} //对外提供获取实例的方法 public static Singleton getSingleton() { return singleton; } }3.2.懒汉式
优点:按需索取,当使用时才初始化,有利于节省内存空间
缺点:第一次加载时不够快,另外需要考虑多线程作用下的同步问题,开销稍微大点
3.2.1 同步方法
public class Singleton { private static Singleton singleton = null; //构造方法私有化 private Singleton(){} // 对外提供获取实例的方法 public static synchronized Singleton getSingleton() { if(singleton == null){ singleton = new Singleton(); } return singleton; } }3.2.2 静态内部类
public class Singleton { private Singleton(){} private static class SingletonHolder{ private static final Singleton sinleton = new Singleton(); } //对外提供获取实例的方法 public static synchronized Singleton getSingleton() { return SingletonHolder.sinleton; } }3.2.3 DCL(Double Check Lock) 双重检测
volatile 此处的主要功能是(禁止重排):告知编译器在标记的变量前后不使用优化功能 。
一般来说一个对象的初始化至少经过以下三步:
1)在堆内存开辟内存空间。
2)在堆内存中实例化对象中的各个参数。
3)把对象指向堆内存空间。
1,2,3 步不会乱序优化 ,所以才避免DCL 失效。
如果没有volatile,优化后可能为1,3,2,就会在第一个判断null时出问题,某一线程拿到已经分配了地址,但未初始化的实例。
public class Singleton { private static volatile Singleton singleton = null; //构造方法私有化 private Singleton(){} // 对外提供获取实例的方法 public static Singleton getSingleton() { if(singleton == null){ synchronized (Singleton.class){ if(singleton == null) { singleton = new Singleton(); } } } return singleton; } }3.2.4 内部枚举类
public class SingletonHolder { enum Singleton{ SINGLETON } public static Singleton getSingleton() { return Singleton.SINGLETON; } }四、关于破坏
除枚举外,可以说其它类都可以通过反射获取实例。
4.1 克隆、序列化举例(饿汉式-静态实例)(未处理)实现接口未做处理
public class Singleton implements Cloneable,Serializable{ private static final Singleton singleton = new Singleton(); //构造方法私有化 private Singleton(){} //对外提供获取实例的方法 public static Singleton getSingleton() { return singleton; } //protected权限范围:同类、子类、同包(不含子孙包) @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }测试
public class SingletonTest{ public static void main(String[] args) throws Exception { System.out.println("克隆---------------------------"); //克隆 Singleton singleton1 = Singleton.getSingleton(); Object clone = Singleton.getSingleton().clone(); System.out.println("singleton1 = "+singleton1); System.out.println("clone = "+singleton1); System.out.println("clone == singleton = "+(singleton1==clone)); System.out.println("序列化------------------反序列化"); //序列化 Singleton singleton2 = Singleton.getSingleton(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.obj")); oos.writeObject(singleton2); oos.flush(); oos.close(); //反序列化读取实例 FileInputStream fis = new FileInputStream("Singleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); Singleton s1 = (Singleton)ois.readObject(); ois.close(); System.out.println("singleton2 = "+singleton2); System.out.println("Singleton.obj = "+s1); System.out.println("singleton2 == Singleton.obj = "+(singleton2==s1)); } }测试结果
4.2 订正后
public class Singleton implements Cloneable,Serializable{ private static final Singleton singleton = new Singleton(); // 构造方法私有化 private Singleton(){} // 对外提供获取实例的方法 public static Singleton getSingleton() { return singleton; } //克隆时,直接返回singleton @Override protected Object clone() throws CloneNotSupportedException { return singleton; } //反序列化时,直接返回singleton public Object readResolve() throws ObjectStreamException { return singleton; } }测试结果
五、枚举三问5.1.枚举能不能克隆???
不能克隆
枚举类继承了java.lang.Enum类,而不是默认的Object类。而在Enum中clone是这样定义的(final不能被覆写)
/** * Throws CloneNotSupportedException. This guarantees that enums * are never cloned, which is necessary to preserve their "singleton" * status. * @return (never returns) */ protected final Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); }5.2 枚举序列化前和反序列化后两者是一个对象吗???
是、是、是 ,是同一个对象 重要事情,说三遍!!!
Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。
5.3 枚举可不可以通过反射获取实例
不能
枚举的直接父类是java.lang.Enum,而java.lang.Enum中没有无参构造器当类加载Enum子类时,首先要干啥?要初始化父类,这就是说只要子类只要不定义无参构造器,系统默认会构造一个有参构造器,来super(),初始化父类。反射中,对于枚举的有参构造器创建实例,是限制创建的。
Enum类构造器
Constructor有参构造限制反射枚举实例。
因此,在构建单例模式时,十分推荐使用枚举。
当然,根据系统需要单例创建可以自行选择创建方式。
标签: #单例模式的几大要素