• 设计模式 - 单例模式理解及相关问题解决方法


    我们都知道在设计模式中有一种模式叫单例模式,主要有两种模式

    1.饿汉模式

    饿汉模式是class被加载时就会被初始化,下面是具体的实例class:

    public class HungrySingleton {

    private static final HungrySingleton hungrySingleton=new HungrySingleton();

    public HungrySingleton(){

    }

    public static HungrySingleton getInstance(){

    return hungrySingleton;

    }

    }

    2.懒汉模式

    懒汉模式就是class未被使用时不会初始化,主要要是为了内存使用效率,饿汉模式在加载后被初始化有可能会用不到,对整个内存是浪费,以下是懒汉模式的实列class:

    public class LazySingleton implements Serializable {

    private static LazySingleton lazySingleton=null;

    public static LazySingleton getInstance(){

    if (lazySingleton==null)

    synchronized (LazySingleton.class){

    lazySingleton=new LazySingleton();

    return lazySingleton;

    }

    else

    return lazySingleton;

    }

    }

    从以上例子来是不是觉得已经很完美了,这些类在整个应用中只可能生成一个对象,但是在java中我们有多种方式生成对象,其中可以利java的反射机制来生成对象,如下:

    1.我们对饿汉模式测试:

    @Test

    public void testHungrySingleton() throws Exception {

    Constructor c= HungrySingleton.class.getDeclaredConstructor(null);

    c.setAccessible(true);

    Object c1=c.newInstance();

    Object c2=c.newInstance();

    System.out.println(c1+" "+c2+" "+(c1==c2));

    c1=HungrySingleton.getInstance();

    c2=HungrySingleton.getInstance();

    System.out.println(c1+" "+c2+" "+(c1==c2));

    }

    进行log:

    com.mesui.model.HungrySingleton@ca263c2 com.mesui.model.HungrySingleton@589b3632 false

    com.mesui.model.HungrySingleton@45f45fa1 com.mesui.model.HungrySingleton@45f45fa1 true

    2.我们对懒汉模式测试:

    @Test

    public void LazySingleton() throws Exception {

    Constructor c= LazySingleton.class.getDeclaredConstructor(null);

    c.setAccessible(true);

    Object c1=c.newInstance();

    Object c2=c.newInstance();

    System.out.println(c1+" "+c2+" "+(c1==c2));

    c1=LazySingleton.getInstance();

    c2=LazySingleton.getInstance();

    System.out.println(c1+" "+c2+" "+(c1==c2));

    }

    运行log:

    com.mesui.model.LazySingleton@ca263c2 com.mesui.model.LazySingleton@589b3632 false

    com.mesui.model.LazySingleton@45f45fa1 com.mesui.model.LazySingleton@45f45fa1 true

    从以上测试来看反射可以破坏单例模式,从上面的例子来看主要利用了反射来调用构造方法来进行会生成两个不同对象,我们的防止方法可组止调用构造方法,我们来懒汉模式来举例:

    public class LazySingleton implements Serializable {

    private static LazySingleton lazySingleton=null;

    public LazySingleton() {

    throw new RuntimeException("you can't new instance object by constructor");

    }

    public static LazySingleton getInstance(){

    if (lazySingleton==null)

    synchronized (LazySingleton.class){

    lazySingleton=new LazySingleton();

    return lazySingleton;

    }

    else

    return lazySingleton;

    }

    }

    我们在利用LazySingleton()进行一次测试,运行log如下:

    java.lang.RuntimeException: you can't new instance object by constructor

    at com.mesui.model.LazySingleton.(LazySingleton.java:9)

    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)

    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)

    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)

    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)

    at com.mesui.SingletonTest.LazySingleton(SingletonTest.java:31)

    这样我们的单例模式还是有问题,如果我们通过我们对像写进文件然后在读出来,这样是否会有问题,我们看看以下测试用例:

    @Test

    public void FileHungrySingleton() throws Exception {

    LazySingleton c1=LazySingleton.getInstance();

    FileOutputStream fos=new FileOutputStream("lazysingleton.obj");

    ObjectOutputStream oos=new ObjectOutputStream(fos);

    oos.writeObject(c1);

    oos.flush();

    fos.close();

    FileInputStream fis=new FileInputStream("lazysingleton.obj");

    ObjectInputStream ois=new ObjectInputStream(fis);

    LazySingleton c2=(LazySingleton) ois.readObject();

    System.out.println(c1+" "+c2+" "+(c1==c2));

    }

    运行log:

    com.mesui.model.LazySingleton@2b4a2ec7 com.mesui.model.LazySingleton@c8e4bb0 false

    有点奇怪,我们的解决方法在class中增加如下方法可以解决

    public class LazySingleton implements Serializable {

    private static LazySingleton lazySingleton=null;

    public static LazySingleton getInstance(){

    if (lazySingleton==null)

    synchronized (LazySingleton.class){

    lazySingleton=new LazySingleton();

    return lazySingleton;

    }

    else

    return lazySingleton;

    }

    private Object readResolve(){

    return lazySingleton;

    }

    }

    我们在利用测试方法FileHungrySingleton()在进行一次,运行log如下:

    com.mesui.model.LazySingleton@2b4a2ec7 com.mesui.model.LazySingleton@2b4a2ec7 true

    从上面各种实例来看单例模式有各种问题,其实我们可以用利emum类来解决,如下:

    public enum EnumSingleton {

    INSTANCE;

    private Object object;

    public void setObject(Object o){

    this.object=o;

    }

    public Object getObject(){

    return this.object;

    }

    public static EnumSingleton getInstance(){

    return INSTANCE;

    }

    }

    我们可以用以下测试用例来覆盖上面的问题,看测试是否存在问题:

    @Test

    public void EnumSingleton() throws Exception {

    EnumSingleton c1=EnumSingleton.getInstance();

    EnumSingleton c2=EnumSingleton.getInstance();

    c1.setObject(new Object());

    System.out.println(c1.getObject());

    c2.setObject(new Object());

    System.out.println(c2.getObject());

    System.out.println(c1.getObject()+" "+c2.getObject()+" "+(c1.getObject()==c2.getObject()));

    EnumSingleton e1=EnumSingleton.INSTANCE;

    e1.setObject(new Object());

    System.out.println(e1.getObject());

    FileOutputStream fos=new FileOutputStream("enumsingleton.obj");

    ObjectOutputStream oos=new ObjectOutputStream(fos);

    oos.writeObject(e1);

    oos.flush();

    fos.close();

    FileInputStream fis=new FileInputStream("enumsingleton.obj");

    ObjectInputStream ois=new ObjectInputStream(fis);

    EnumSingleton e2=(EnumSingleton) ois.readObject();

    System.out.println(e2.getObject());

    System.out.println(e1.getObject()==e2.getObject());

    Constructor c= EnumSingleton.class.getDeclaredConstructor(null);

    c.setAccessible(true);

    Object o1=c.newInstance();

    Object o2=c.newInstance();

    System.out.println(o1+" "+o2+" "+(o1==o2));

    }

    测试log:

    java.lang.Object@ca263c2

    java.lang.Object@589b3632

    java.lang.Object@589b3632 java.lang.Object@589b3632 true

    java.lang.Object@45f45fa1

    java.lang.Object@45f45fa1

    true

    java.lang.NoSuchMethodException: com.mesui.model.EnumSingleton.()

    at java.lang.Class.getConstructor0(Class.java:3082)

    at java.lang.Class.getDeclaredConstructor(Class.java:2178)

    从以上的测试结果可以看出利用反射和文件写读都破坏不了单便模式,当然我们还可以利用其他方式比如threadlocal或容器方式也可以来解决

  • 相关阅读:
    c#开发和学习(c#编写windows服务)
    38.JavaScript中异步与回调的基本概念,以及回调地狱现象
    CTF-sql注入(X-Forwarded-For)【简单易懂】
    某Al行业四小龙之一:向空间要效率之前,向流程要效率
    Transformer模型 | iTransformer时序预测
    CPU、GPU、NPU的区别
    TODO Vue typescript forEach的bug,需要再核實
    Electron开发环境准备
    C++ 移动语义学习
    基于SSH的客户关系管理系统的设计与实现(JavaWeb开发的CRM管理系统)
  • 原文地址:https://blog.csdn.net/lin000_0/article/details/127569517