创建型模式提供创建对象的机制,能够提升已有代码的灵活性和复用性;
常用的有:单例模式、工厂模式、建造设模式;不常用的:原型模式;
单例模式是最简单的模式之一,其保证了某个类在运行期间只有一个实例对外提供服务;
单例模式有两种实现方式:饿汉式与懒汉式
在类加载期间初始化私有的静态实例,保证实例在创建过程是线程安全的;
不支持懒加载,获取速度较快,但是对象大且不常用的话会造成内存的浪费;
/** * @author : luobei * @date : 2024/7/31 14:36 * 单例模式-饿汉式 */ public class SingletonHungry { // 1.私有构造方法 private SingletonHungry(){} // 2.在本类中创建私有静态的全局对象 private static SingletonHungry instance = new SingletonHungry(); // 3.提供一个全局访问点 public static SingletonHungry getInstance(){ return instance; } }
支持懒加载,只有第一次调用getInstance方法时才会创建对象,只有在getInstance方法上添加sychronized锁才能保证线程安全,但加了锁后并发度会比较低,因此不适合常用的类;
/** * @author : luobei * @date : 2024/7/31 14:49 * 单例模式-懒汉式(线程不安全) */ public class SingletonLazy { // 1.私有构造方法 private SingletonLazy(){} // 2.在本类中创建私有静态的全局对象 private static SingletonLazy instance; // 3.提供一个全局访问点 public static SingletonLazy getInstance(){ //通过判断对象是否被初始化,来选择是否创建对象 if(instance == null){ instance = new SingletonLazy(); } return instance; } }
或者可以使用模块锁,二次判断来保证创建完成之后的getInstance调用的并发度,而且本类中的静态变量需要添加volatile修饰,保证变量可见性,避免指令重排;
指令重排是指 jvm 会根据性能自动重新排序创建对象时的顺序,所以有的时候过多的线程可能会导致上一个线程对象创建了一半,此时实例不为null但是却不完整,下一个线程拿去用的话会导致报错;
/** * @author : luobei * @date : 2024/7/31 14:49 * 单例模式-双重验证懒汉式(线程安全) */ public class SingletonLazy { // 1.私有构造方法 private SingletonLazy(){} // 2.在本类中创建私有静态的全局对象 private volatile static SingletonLazy instance; // 3.提供一个全局访问点 public static SingletonLazy getInstance(){ //通过判断对象是否被初始化,来选择是否创建对象 if(instance == null){ synchronized (SingletonLazy.class){ //抢到锁之后再次进行判断是否为null if(instance == null){ instance = new SingletonLazy(); } } } return instance; } }
既保证了线程安全,又可以懒加载,而且比双重验证懒汉式简洁
/** * @author : luobei * @date : 2024/7/31 14:49 * 单例模式-静态内部类 */ public class SingletonStatic { // 1.私有构造方法 private SingletonStatic(){} // 2.创建静态内部类 private static class SingletonHandler{ //在静态内部类中创建单例,在装载内部类的时候才会创建单例对象 private static SingletonStatic instance = new SingletonStatic(); } // 3.提供一个全局访问点 public static SingletonStatic getInstance(){ return SingletonHandler.instance; } }
即便构造方法私有化,通过反射依然能够创建实例;
Class singletonClass = SingletonStatic.class; Constructor declaredConstructor = singletonClass.getDeclaredConstructor(); declaredConstructor.setAccessible(true); SingletonStatic singletonStatic = declaredConstructor.newInstance();
在构造方法中添加验证
// 1.私有构造方法 private SingletonStatic(){ if(SingletonHandler.instance != null){ throw new RuntimeException("单例模式禁止反射调用"); } }
//序列化对象输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile.obj")); oos.writeObject(SingletonStatic.getInstance()); //序列化对象输入流 File file = new File("tempFile.obj"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); SingletonStatic singletonStatic = (SingletonStatic)ois.readObject();
这是因为 readObject() -> readObject() -> readObject0() -> checkResolve() -> readOrdinaryObject(unshared)中的
只需要在对象中添加 readResolve() 方法
private Object readResolve(){ return SingletonHandler.instance; }
这是因为 readObject() -> readObject() -> readObject0() -> checkResolve() -> readOrdinaryObject(unshared)中的
/** * @author : zluobei * @date : 2024/7/31 14:49 * 单例模式-枚举 */ public enum SingletonEnum { INSTANCE; public static SingletonEnum getInstance(){ return INSTANCE; } }
首先能防止反射是因为 newInstance() -> newInstanceWithCaller() 里的
反射在枚举上的应用有限,可以通过反射获取枚举类的信息和调用方法,但不能通过反射创建新的枚举实例。
然后枚举类能防止序列化是因为,Java规范字规定每个枚举类型及其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定:在序列化的时候Java仅仅将枚举对象的属性名字输到结果中,反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象。因此序列化前后的对象相同。