设计模式_单例模式

创建型设计模式

单例模式

简介: 令某一个类在程序中只存在一个实例

实例化方法

饿汉式:基本方式

在载入JVM虚拟机时直接实例化

我们知道,类加载的方式是按需加载,且加载一次。因此,在下述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

class Singleton {
    private final static Singleton INSTANCE = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

public class Demo1 {
    public static void main(String[] args) {
        for (int i = 1; i <= 999; ++i) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Singleton instance = Singleton.getInstance();
                    System.out.println(instance);  // 打印结果都一样
                }
            }).start();
        }
    }
}

懒汉式:基本方式

做到懒加载(懒汉式需要考虑线程安全问题)

优点:当需要时对对象进行实例化,节约了内存

缺点:存在线程同步问题,需要加锁,然而此种方法对getInstance()进行加锁,导致即使instance已经不为null,仅仅只单纯获取对象时依然受到锁的限制,效率过低。

class Singleton {
    private static volatile Singleton instance; // volatile保证线程同步

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

懒汉式:双重校验锁

保留了基本方式的优点,也解决了基本方式的缺点

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;
    }
}

懒汉式:静态内部类

静态内部类单例模式由内部类创建,由于JVM在加载外部类时,不会加载静态内部类,只要当静态内部类被访问时才会进行加载,并初始化静态变量,静态变量被static修饰保证只会被实例化一次,切严格保证实例化顺序。

class Singleton {
     private Singleton() {}
     
     private static class SingletonHolder {
         private final static Singleton INSTANCE = new Singleton();
     }
 
     public static Singleton getInstance() {
         return SingletonHolder.INSTANCE;
     }   
 }

image

双重校验锁与静态内部类都是非常推荐的懒汉式单例模式

饿汉式:枚举

image

enum Singleton {
    /**
     * Singleton实例
     */
    INSTANCE
}

public class Demo {
    public static void main(String[] args) {
        for (int i = 1; i <= 999; ++i) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Singleton instance = Singleton.INSTANCE;
                    System.out.println(instance.hashCode());
                }
            }).start();
        }
    }
}

总结

一般情况下,不建议使用懒汉式基本方式,建议使用饿汉式基本方式。只有在要明确实现 lazy loading 效果时,才会使用静态内部类方式。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用双重校验锁方式。

破坏单例模式

序列化与反序列化

序列化:对象➡字节串;是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

目的: 1、以某种存储形式使自定义对象持久化;2、将对象从一个地方传递到另一个地方;

反序列化:字节串➡对象;将存储的对象信息重新转换为对象;

目的:1、将持久化后的对象重新实例化;2、接收其他地方传递来的对象

class Singleton implements Serializable {
    private final static Singleton INSTANCE = new Singleton();
    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
public class Demo1 {
    public static void main(String[] args) throws Exception {
        writeSerializable();
        Singleton singleton = readSerializable();
        Singleton singleton1 = readSerializable();
        System.out.println(singleton == singleton1);
    }

    public static void writeSerializable() throws Exception {
        Singleton instance = Singleton.getInstance();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(
                new FileOutputStream("C:\\Users\\80946\\Desktop\\temp.txt"));
        objectOutputStream.writeObject(instance);
        objectOutputStream.close();
    }

    public static Singleton readSerializable() throws Exception {
        ObjectInputStream inputStream = new ObjectInputStream(
                new FileInputStream("C:\\Users\\80946\\Desktop\\temp.txt"));
        Singleton singleton = (Singleton) inputStream.readObject();
        inputStream.close();
        return singleton;
    }
}

输出结果:

false

由此可见,Singleton在本程序中出现了两个实例,违背的单例模式。序列化与反序列化破坏了单例模式。

解决方案

Singleton类中添加 readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new除的对象

此解决方案与inputStream.readObject()的内部实现有关。

class Singleton implements Serializable {
    private final static Singleton INSTANCE = new Singleton();
    private Singleton() {}

    public Object readResolve() {
        return INSTANCE;
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

public class Demo1 {
    public static void main(String[] args) {
        writeSerializable();
        Singleton singleton = readSerializable();
        Singleton singleton1 = readSerializable();
        System.out.println(singleton == singleton1);
    }

    public static void writeSerializable() {
        Singleton instance = Singleton.getInstance();
        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(
                new FileOutputStream("C:\\Users\\80946\\Desktop\\temp.txt"))) {
            objectOutputStream.writeObject(instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Singleton readSerializable() {
        Singleton singleton = null;
        try (ObjectInputStream inputStream = new ObjectInputStream(
                new FileInputStream("C:\\Users\\80946\\Desktop\\temp.txt"))) {
            singleton = (Singleton) inputStream.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return singleton;
    }
}

输出结果:

true

反序列化得到的时同一对象,遵守了单例模式

反射

Java反射:在运行状态中,程序可以拿到一个类或者一个对象的所有属性和方法,并对其进行修改。

class Singleton {
    private final static Singleton INSTANCE = new Singleton();
    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
public class Demo1 {
    public static void main(String[] args) throws Exception {
        Class<Singleton> singletonClass = Singleton.class;
        Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        Singleton singleton = declaredConstructor.newInstance();
        Singleton singleton1 = declaredConstructor.newInstance();
        System.out.println(singleton == singleton1);
    }
}

输出结果:

false

由此可见通过反射可以改变构造函数的可见性,导致可以通过构造函数直接

解决方案

饿汉式

class Singleton {
    private final static Singleton INSTANCE = new Singleton();
    /**
     * 当单例对象已经被实现后,如果构造方法再次被调用,就会发生异常
     */
    private Singleton() {
        if (INSTANCE != null) {
            throw new RuntimeException("不能创建多个对象");
        }
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

懒汉式

懒汉式存在多线程问题,故处理方式和饿汉式不一样

// 静态内部类方式,其他的懒汉式也可以这样处理
class Singleton {

    private volatile static boolean flag = false;

    private Singleton() {
        synchronized (Singleton.class) {
            if (flag) {
                throw new RuntimeException();
            }
            flag = true;
        }
    }

    private static class SingletonHolder {
        private final static Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
public class Demo1 {
    public static void main(String[] args) throws Exception {

        for (int i = 0; i <= 99; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Class<Singleton> singletonClass = Singleton.class;
                    Singleton singleton = null;
                    Singleton singleton1 = null;
                    try {
                        Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor();
                        declaredConstructor.setAccessible(true);
                        singleton = declaredConstructor.newInstance();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println(singleton);
                }
            }).start();
        }
        Singleton instance = Singleton.getInstance();
        System.out.println(instance);
    }
}

输出结果:

仅有一个输出不为null,故Singleton只实例化了一次

但经测试,此时类已被破坏,既即使不通过反射,通过正常方式也无法获取对象

原因分析

只能得到第一次反射创建的那个对象,但这个对象似乎无法存到SingletonHolder.INSTANCE中。。导致使用正常方式获取单例对象时,就会执行Singleton INSTANCE = new Singleton(); 结果就又异常了,其他的懒汉式也有这种问题。。。。(我不会解决)
饿汉式没有这种问题

碰巧的解决方法

// 仅限懒汉式:静态内部类法使用
// 其他方法使用会栈溢出
class Singleton {

    private volatile static boolean flag = false;

    private Singleton() {
        synchronized (Singleton.class) {
        	Singleton instance = Singleton.getInstance();
            if (flag) {
                throw new RuntimeException();
            }
            flag = true;
        }
    }

    private static class SingletonHolder {
        private final static Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
public class Demo1 {
    public static void main(String[] args) throws Exception {

        for (int i = 0; i <= 99; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Class<Singleton> singletonClass = Singleton.class;
                    Singleton singleton = null;
                    Singleton singleton1 = null;
                    try {
                        Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor();
                        declaredConstructor.setAccessible(true);
                        singleton = declaredConstructor.newInstance();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println(singleton);
                }
            }).start();
        }
        Singleton instance = Singleton.getInstance();
        System.out.println(instance);
    }
}
posted @ 2022-01-02 20:57  Voca  阅读(43)  评论(0)    收藏  举报