创建型-单例模式(Singleton Pattern)

1. 单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例设计模式分类有两种:

  • 饿汉式:类加载就会导致该单实例对象被创建
  • 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)

1.1. 饿汉式-静态常量方式(保证实例一定用到的情况下可以使用)

public class Singleton {

    //1.私有构造器,不提供给外界使用
    private Singleton() {
    }

    //2.通过静态常量使用构造器
    private final static Singleton instance = new Singleton();

    //提供外界获取实例对象
    public static Singleton getInstance() {
        return instance;
    }
}

1.2. 饿汉式-静态代码块方式(保证实例一定用到的情况下可以使用)

public class Singleton {

    private Singleton() {
    }

    private static Singleton instance;

    static {
        instance = new Singleton();
    }

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

1.3. 懒汉式-线程不安全(不推荐使用)

public class Singleton {
    private Singleton() {
    }

    private static Singleton instance;

    public static Singleton getInstance() {
        //保证只有一个实例对象
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

1.4. 懒汉式-线程安全-同步方法(不推荐使用)

public class Singleton {
    private Singleton() {
    }

    private static Singleton instance;

    //加上synchronized同步关键字保证线程安全
    public static synchronized Singleton getInstance() {
        //保证只有一个实例对象
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

1.5. 懒汉式-线程安全-同步代码块(不推荐使用)

public class Singleton {
    private Singleton() {
    }

    private static Singleton instance;

    //加上synchronized同步关键字保证线程安全
    public static synchronized Singleton getInstance() {
        //保证只有一个实例对象
        if (instance == null) {
            //同步代码块
            synchronized(Singleton.class){
                instance = new Singleton();   
            }
        }
        return instance;
    }
}

1.6. 懒汉式-双重检查锁模式(推荐使用)

public class Singleton {
    private Singleton() {
    }

    private static volatile Singleton instance;

    // 加上synchronized同步关键字保证线程安全
    public static Singleton getInstance() {
        // 判断是否加锁,写操作加锁,读操作不加锁
        if (instance == null) {
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。

解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。

1.7. 懒汉式-静态内部类(推荐使用)

public class Singleton {
    private Singleton() {
    }

    
    // 定义一个静态内部类
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

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

1.8. 枚举方式(饿汉式)(推荐使用)

public enum SingletonEnum {
    INSTANCE;
    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public static void main(String[] args) {
        SingletonEnum instance = SingletonEnum.INSTANCE;
        instance.setValue("Hello, World!");
        System.out.println(instance.getValue());
    }

枚举类实现单例模式是唯一一种不会被破坏的单例实现模式

存在的问题

通过序列化反序列化破坏单例模式

public class Singleton implements Serializable {
    private Singleton() {
    }

    // 定义一个静态内部类
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
public class Client {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // writeObject2File();
        readObjectFromFile();
        readObjectFromFile();
    }

    // 从文件读取数据
    public static void readObjectFromFile() throws IOException, ClassNotFoundException {
        // 创建对象输入流对象
        ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("C:\\Users\\Better\\Desktop\\test.txt")));
        //读对象
        Singleton instance = (Singleton)ois.readObject();
        System.out.println(instance);
        // 释放资源
        ois.close();
    }

    // 向文件中写数据
    public static void writeObject2File() throws IOException {
        Singleton instance = Singleton.getInstance();
        // 创建对象输出流对象
        ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("C:\\Users\\Better\\Desktop\\test.txt")));
        // 写对象
        oos.writeObject(instance);
        // 关闭资源
        oos.close();
    }
}

结果:存在两个实例代表单例模式被破坏

通过反射破坏单例模式

public class Singleton {

    // 私有构造方法
    private Singleton() {
    }

    private static volatile Singleton instance;

    // 对外提供静态方法获取该对象
    public static Singleton getInstance() {

        if (instance != null) {
            return instance;
        }

        synchronized (Singleton.class) {
            if (instance != null) {
                return instance;
            }
            instance = new Singleton();
            return instance;
        }
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        // 获取Singleton类的字节码对象
        Class<Singleton> clazz = Singleton.class;
        // 获取Singleton类的私有无参构造方法对象
        Constructor<Singleton> constructor = clazz.getDeclaredConstructor();
        // 取消访问检查
        constructor.setAccessible(true);

        // 创建Singleton类的对象s1
        Singleton s1 = constructor.newInstance();
        // 创建Singleton类的对象s2
        Singleton s2 = constructor.newInstance();

        // 判断通过反射创建的两个Singleton对象是否是同一个对象
        System.out.println(s1 == s2);
    }
}

输出结果为false

解决问题

解决序列化反序列化破坏单例模式的方法

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

public class Singleton implements Serializable {
    private Singleton() {
    }

    // 定义一个静态内部类
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

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

    //!!!当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
    public Object readResolve() {
        return SingletonHolder.INSTANCE;
    }
}

源码解析

public final Object readObject() throws IOException, ClassNotFoundException{
    ...
    // if nested read, passHandle contains handle of enclosing object
    int outerHandle = passHandle;
    try {
        Object obj = readObject0(false);//重点查看readObject0方法
    .....
}
    
private Object readObject0(boolean unshared) throws IOException {
	...
    try {
		switch (tc) {
			...
			case TC_OBJECT:
				return checkResolve(readOrdinaryObject(unshared));//重点查看readOrdinaryObject方法
			...
        }
    } finally {
        depth--;
        bin.setBlockDataMode(oldMode);
    }    
}
    
private Object readOrdinaryObject(boolean unshared) throws IOException {
	...
	//isInstantiable 返回true,执行 desc.newInstance(),通过反射创建新的单例类,
    obj = desc.isInstantiable() ? desc.newInstance() : null; 
    ...
    // 在Singleton类中添加 readResolve 方法后 desc.hasReadResolveMethod() 方法执行结果为true
    if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {
    	// 通过反射调用 Singleton 类中的 readResolve 方法,将返回值赋值给rep变量
    	// 这样多次调用ObjectInputStream类中的readObject方法,继而就会调用我们定义的readResolve方法,所以返回的是同一个对象。
    	Object rep = desc.invokeReadResolve(obj);
     	...
    }
        return obj;
}

解决反射破坏单例模式的方法

public class Singleton {
    // 私有构造方法
    private Singleton() {
        //  反射破解单例模式需要添加的代码
        if (instance != null) {
            throw new RuntimeException("已经创建对象,不能再创建");
        }
    }

    private static volatile Singleton instance;

    // 对外提供静态方法获取该对象
    public static Singleton getInstance() {

        if (instance != null) {
            return instance;
        }

        synchronized (Singleton.class) {
            if (instance != null) {
                return instance;
            }
            instance = new Singleton();
            return instance;
        }
    }
}

JDK中的案例-Runtime类

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }
    private Runtime() {}
    
    //其它代码省略
    ......
}

可以看出Runtime类使用了单例模式的饿汉式-静态成员变量方式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值