Java设计模式001 --- 单例模式
Posted 光头用沙宣
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java设计模式001 --- 单例模式相关的知识,希望对你有一定的参考价值。
前言
什么是单例模式?就是在一个应用程序中,一个类的实例有且仅有一个;这个类负责创建该类的实例;
一般来说单例是有状态的对象,比如全局设置、数据库dao实例、全局资源等,并且可以根据需求延迟加载或者即时加载;
即时加载单例模式
1、静态公有域单例(我不习惯别人说的饿汉、懒汉)
public class Singleton1 { public static final Singleton1 INSTANCE = new Singleton1(); // 私有构造器, 防止被实例化 private Singleton1() { } public void doWhatever() { } }
使用方法:SingleTon1.INSTANCE
特点:在类加载的时候就初始化好了,无线程安全问题;
即时加载,但是存在单例被破坏的风险,如使用反射、序列化
反射方式:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class<Singleton1> clazz = Singleton1.class; Constructor<Singleton1> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); Singleton1 singleton1 = constructor.newInstance(); System.out.println(singleton1 == Singleton1.getInstance()); }
序列化方式(前提是单例类实现了Serializale):
public static void main(String[] args) { Singleton1 singleton = Singleton1.getInstance(); File file = new File("src\\\\main\\\\resources\\\\singleton.txt"); try (FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(singleton); } catch (IOException ex) { ex.printStackTrace(); } try (FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis)) { Singleton1 singleton1 = (Singleton1) ois.readObject(); System.out.println(singleton == singleton1); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } }
2、上述问题的解决方案
为了解决反射对单例造成的破坏,可以做如下修改:在私有构造方法中判断实例是否为null,否则抛运行时异常
// 私有构造器, 防止被实例化 private Singleton1() { if (instance != null) { throw new RuntimeException(); } }
上述方法对序列化不起作用,这也从侧面验证了反序列化创建的对象不依赖类的构造器,而是由JVM创建的;
为了解决序列化对单例造成的破坏,可做如下修改:在序列化类中添加私有readResolve方法
private Object readResolve() { return instance; }
3、静态工厂方法单例
public class SingletonFactory implements Serializable { private static final SingletonFactory INSTANCE = new SingletonFactory(); private SingletonFactory() { if (INSTANCE != null) { throw new RuntimeException(); } } public static SingletonFactory getInstance() { return INSTANCE; } private Object readResolve() { return INSTANCE; } public static void main(String[] args) { SingletonFactory instance1 = SingletonFactory.getInstance(); // 序列化 File file = new File("src\\\\main\\\\resources\\\\serial\\\\serial_singleton.txt"); try (FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(instance1); } catch (IOException ex) { ex.printStackTrace(); } // 反序列化 try (FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis)) { SingletonFactory instance2 = (SingletonFactory) ois.readObject(); System.out.println(instance1 == instance2); } catch (IOException | ClassNotFoundException ex) { ex.printStackTrace(); } // 反射 try { Constructor<SingletonFactory> constructor = SingletonFactory.class.getDeclaredConstructor(); SingletonFactory instance3 = constructor.newInstance(); System.out.println(instance1 == instance3); } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } } }
和静态公有域相比,静态工厂方法更灵活,可以修改单例为延迟加载、甚至可以改成非单例
4、枚举单例模式
虽然有以上方法可以解决反射和序列化带来的问题,但是有更简便的单例模式 --- 枚举单例
枚举单例有如下几个有点:线程安全、能够防止反射和序列化带来的破坏、实现简单
public enum SingletonEnum { INSTANCE; public void doWhatever() { System.out.println("Single Enum."); } }
使用方法:SingletonEnum.INSTANCE.doWhatever()
验证下反射和序列化场景是否会破坏单例:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { SingletonEnum instance = SingletonEnum.INSTANCE; // 验证两次获取的是否是同一个对象 System.out.println(instance == SingletonEnum.INSTANCE); // 序列化场景 File file = new File("src\\\\main\\\\resources\\\\singleton.txt"); try (FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(instance); } catch (IOException ex) { ex.printStackTrace(); } try (FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis)) { SingletonEnum instance2 = (SingletonEnum) ois.readObject(); System.out.println(instance == instance2); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } // 反射场景 Class<SingletonEnum> clazz = SingletonEnum.class; Constructor<SingletonEnum> constructor = clazz.getDeclaredConstructor(String.class, int.class); constructor.setAccessible(true); SingletonEnum instance1 = constructor.newInstance(); System.out.println(instance == instance1); }
运行结果:
为啥使用反射的时候会抛异常呢?这是因为反射的源码是这样写的:
那么又为啥枚举单例能够保证单例不被序列化破坏呢?
这是因为Java中规定,每个枚举变量在JVM中都是唯一的,并且Java还规定,枚举类在反序列化时使用枚举类的valueOf方法
枚举类反编译结果如下:
线程安全又是为什么?从反编译结果就能看出,INSTANCE是静态的,并且初始化时就创建了实例
延迟加载
有些场景下单例需要延迟加载,比如:(1)单例加载的资源较多,并且使用率低;(2)依赖其他延迟加载资源,即时加载导致数据不完整;
1、静态内部类单例
public class StaticInnerSingleton { private static class SingleInner { private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton(); } private StaticInnerSingleton() { } public static StaticInnerSingleton getInstance() { return SingleInner.INSTANCE; } }
2、双重锁模式
public class SingleLock { private static volatile SingleLock instance; private SingleLock() { } public static SingleLock getInstance() { // 降低加锁带来的性能开销 if (instance != null) { synchronized (SingleLock.class) { // 防止被实例化多次 if (instance != null) { instance = new SingleLock(); } } } return instance; } }
这种方法性能较差,建议延迟加载使用静态内部类单例模式
spring中的单例
spring中的单例指的是在spring IOC容器中只有一个实例,但是一个java程序中可以有多个spring IOC容器,并不保证java应用程序中的单例
以上是关于Java设计模式001 --- 单例模式的主要内容,如果未能解决你的问题,请参考以下文章