龙空技术网

关于单例模式的几个问题

一只奋斗的猿 172

前言:

当前你们对“单例模式的几大要素”大致比较重视,你们都想要剖析一些“单例模式的几大要素”的相关内容。那么小编在网摘上搜集了一些关于“单例模式的几大要素””的相关文章,希望我们能喜欢,咱们一起来了解一下吧!

一、关于创建

单例模式是创建型模式中的一种设计模式:其实例在整个应用全局中有且只有一个,并且该实例由自身创建,不允许被克隆、反序列、反射。为了满足这些要求,一个标准的单例模式一般需包含以下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等方法。

Enum类

5.3 枚举可不可以通过反射获取实例

不能

枚举的直接父类是java.lang.Enum,而java.lang.Enum中没有无参构造器当类加载Enum子类时,首先要干啥?要初始化父类,这就是说只要子类只要不定义无参构造器,系统默认会构造一个有参构造器,来super(),初始化父类。反射中,对于枚举的有参构造器创建实例,是限制创建的。

Enum类构造器

无无参构造器

Constructor有参构造限制反射枚举实例。

Constructor通过有参构造器实例化

因此,在构建单例模式时,十分推荐使用枚举。

当然,根据系统需要单例创建可以自行选择创建方式。

标签: #单例模式的几大要素