3) Singleton pattern

发布时间 2023-06-06 13:27:42作者: zno2

类别:

creational Pattern

问题/动机:

反复创建对象开销巨大耗时长消耗内存/重复使用

方案:

 

 

示例:

 

// 1
class Demo1 {
    public final static Demo1 INSTANCE = new Demo1();

    private Demo1() {
        if (INSTANCE != null)
            throw new RuntimeException("Singleton instance alread exist.");
    }
}

// 2
class Demo2 {
    private final static Demo2 INSTANCE = new Demo2();

    private Demo2() {
        if (INSTANCE != null)
            throw new RuntimeException("Singleton instance alread exist.");
    }

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

// 3
class Demo3 {
    private static volatile Demo3 INSTANCE;

    private Demo3() {
        if (INSTANCE != null)
            throw new RuntimeException("Singleton instance alread exist.");
    }

    public static Demo3 getInstance() {
        if (INSTANCE != null)
            return INSTANCE;
        synchronized (Demo3.class) {
            if (INSTANCE == null)
                INSTANCE = new Demo3();
        }
        return INSTANCE;
    }
}

// 4
enum Demo4 {
    INSTANCE;
}

 

①/② 同理,饿汉加载(tips:类加载机制线程安全,不能延迟加载,不需要同步效率高)

③ 懒汉加载

④ 最优方式

⑤ 静态内部类(安全,可延迟)

击破:

 1. 反序列化击破单例

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class SingletonDemo {
    public static void main(String[] args) throws Exception {
        Demo1 instance = Demo1.INSTANCE;
        // 存储到 demo1_serializable 文件
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("demo1_serializable"));
        oo.writeObject(instance);
        oo.close();
        
        // 从 demo1_serializable 反序列化
        ObjectInputStream oi = new ObjectInputStream(new FileInputStream("demo1_serializable"));
        Object readObject = oi.readObject();
        oi.close();
        
        // 结果是 false ,通过反序列化击破了单例模式 
        System.out.println(instance == readObject);
    }
}

class Demo1 implements Serializable {
    private static final long serialVersionUID = 1L;
    public final static Demo1 INSTANCE = new Demo1();

    private Demo1() {
        if (INSTANCE != null)
            throw new RuntimeException("Singleton instance alread exist.");
    }
}

弥补: 不实现序列化接口,或者不采用 ①/②/③

2. 通过 Clone 击破单例

public class SingletonDemo {
    public static void main(String[] args) {
        Demo1 instance = Demo1.INSTANCE;
        Demo1 clone = instance.clone();

        // 结果是 false ,通过克隆击破了单例模式
        System.out.println(instance == clone);
    }
}

class Demo1 implements Cloneable {
    public final static Demo1 INSTANCE = new Demo1();

    private Demo1() {
        if (INSTANCE != null)
            throw new RuntimeException("Singleton instance alread exist.");
    }

    @Override
    public Demo1 clone() {
        try {
            return (Demo1) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException("Clone not supported");
        }
    }
}

弥补:不实现Cloneable 接口,或者不采用①/②/③

3. 通过反射击破单例

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class SingletonDemo {
    public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
            IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        Demo1 instance = Demo1.INSTANCE;

        Constructor<Demo1> declaredConstructor = Demo1.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        Demo1 newInstance = declaredConstructor.newInstance();

        // 结果是 false ,通过反射击破了单例模式
        System.out.println(instance == newInstance);
    }
}

class Demo1 {
    public final static Demo1 INSTANCE = new Demo1();

    private Demo1() {
    }
}

弥补: 在构造函数中判断,如果实例已经存在,则抛出异常

    private Demo1() {
        if (INSTANCE != null)
            throw new RuntimeException("Singleton instance alread exist.");
    }

 

4. 通过枚举类型强化单例模式

1) 反射找不到方法 java.lang.NoSuchMethodException:

2)无法覆盖 clone 方法

3)自带序列化,反序列化后是同一个对象

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SingletonDemo {
    public static void main(String[] args) throws Exception {
        Demo1 instance = Demo1.INSTANCE;
        // 存储到 demo1_serializable 文件
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("demo1_serializable"));
        oo.writeObject(instance);
        oo.close();

        // 从 demo1_serializable 反序列化
        ObjectInputStream oi = new ObjectInputStream(new FileInputStream("demo1_serializable"));
        Object readObject = oi.readObject();
        oi.close();

        // 结果是 true ,通过枚举强化了单例模式
        System.out.println(instance == readObject);
    }
}

enum Demo1 {
    INSTANCE;
}

 

应用:

数据库连接池?

servlet?

Spring MVC controller ?

 

类加载机制如何保证线程安全?

 

补充:

//5
class Demo5{
    
    private Demo5(){
        throw new RuntimeException("Singleton instance alread exist.");
    }
    
    private static class Inner{
        private static Demo5 demo5 = new Demo5();
    }
    
    public static Demo5 getInstance(){
        return Inner.demo5;
    }
}

 

https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

A nested class is a member of its enclosing class. Non-static nested classes (inner classes) have access to other members of the enclosing class, even if they are declared private. Static nested classes do not have access to other members of the enclosing class. As a member of the OuterClass, a nested class can be declared privatepublicprotected, or package private. (Recall that outer classes can only be declared public or package private.)

Note: A static nested class interacts with the instance members of its outer class (and other classes) just like any other top-level class. In effect, a static nested class is behaviorally a top-level class that has been nested in another top-level class for packaging convenience.