做洁净的网站,h5设计平台,网站诊断案例,怎么创建一个软件1.业务需求
大家好#xff0c;我是菠菜啊。80、90后还记得计划生育这个国策吗#xff1f;估计同龄的小伙伴们#xff0c;小时候常常被”只生一个好“”少生、优生“等宣传标语洗脑#xff0c;如今国家已经放开并鼓励生育了。话说回来#xff0c;现实生活中有计划生育我是菠菜啊。80、90后还记得计划生育这个国策吗估计同龄的小伙伴们小时候常常被”只生一个好“”少生、优生“等宣传标语洗脑如今国家已经放开并鼓励生育了。话说回来现实生活中有计划生育你知道设计模式中也有计划生育吗它是怎么实现的? 2.代码实现
我们只要保证一个类只有一个实例化对象这样就能达到计划生育的目的。其实这个设计模式大家应该都很熟悉了叫做单例模式。
实现思路
类的实例化交给类本身对外提供一个访问该单例的全局访问点重点考虑线程安全、系统资源消耗、反射以及反序列化破坏等因素。
2.1 静态代码块
public class StaticBlockSingleton {private static StaticBlockSingleton singleton;static {singletonnew StaticBlockSingleton();}private StaticBlockSingleton(){}public static StaticBlockSingleton getInstance(){return singleton;}
}2.2 饿汉式
public class HungrySingleton {private static HungrySingleton singletonnew HungrySingleton();private HungrySingleton(){}public static HungrySingleton getInstance(){return singleton;}
}思考静态代码块和饿汉式不管singleton对象有没有被使用都会在系统初始化的时候初始化对象从而占用系统资源。
2.3 懒汉式
public class LazySingleton implements Serializable {//volatile 防止指令重排private static volatile LazySingleton singleton;private LazySingleton(){}public static LazySingleton getInstance(){//1.第一层检索 提高执行效率 如果不是null 直接返回if(nullsingleton){//2.多个线程同时进入 获取锁的执行,没有获取锁的等待synchronized (LazySingleton.class){//3.防止2步骤有等待锁的线程 锁释放后拿到锁后需判断一下对象是否创建 if (nullsingleton){singletonnew LazySingleton();}}}return singleton;}
}思考**双重检查锁定(Double-Check Locking)**懒汉式让对象实例化延迟加载减少了对象未被使用而占用系统资源但是引入了锁系统性能有一定影响。volatile关键字会屏蔽Java虚拟机所做的一些代码优化也可能会导致系统运行效率降低。
拓展指令重排
问题
为什么DCL实现单例还需要用volatile修饰实例呢?
分析
问题出现在‘singletonnew LazySingleton();’这行代码java创建对象不是一个原子操作可以被分解为3步
//1.分配对象的内存空间
//2.初始化对象
//3.将instance指向刚分配的内存地址编译器或者处理器在执行代码的时候为了最大地提高性能可能会将执行执行顺序重排2和3执行顺序可能是相反的。在单线程情况下重排序没有什么问题因为他最终结果都是一致的。但是如果在多线程并发下就会有可能有问题了。下面模拟俩个线程创建单例的场景
CPU时间片线程A线程BT1A-1:分配singleton对象的内存空间T2A-3:将instance指向刚分配的内存地址B-1:第一层判断instance是否为nullT3B-2:instance不为null,B线程获得instance引用的对象T4A-2:初始化对象T5A-4:A线程获得instance引用的对象 如果按照上面的顺序B线程获取到的是一个未初始化的对象这就有问题了。解决方案就是引入volatile关键字它有俩个作用一是保证变量的内存可见性二是禁止指令重排。
保证变量内存可见性
如果属性被volatile修饰相当于会告诉CPU对当前属性的操作不允许使用CPU的缓存必须去和主内存操作。
volatile的内存语义
volatile属性被写当写一个volatile变量JMM会将当前线程对应的CPU缓存及时的刷新到主内存中volatile属性被读当读一个volatile变量JMM会将对应的CPU缓存中的内存设置为无效必须去主内存中重新读取共享变量
禁止指令重排
当我们使用 volatile 关键字来修饰一个变量时Java 内存模型会插入内存屏障一个处理器指令可以对 CPU 或编译器重排序做出约束来确保以下两点
写屏障Write Barrier当一个 volatile 变量被写入时写屏障确保在该屏障之前的所有变量的写入操作都提交到主内存。读屏障Read Barrier当读取一个 volatile 变量时读屏障确保在该屏障之后的所有读操作都从主内存中读取。
2.4 静态内部类
public class StaticInnerSingleton implements Serializable {private static class InnerSingleton{private static final StaticInnerSingleton instance new StaticInnerSingleton();}private StaticInnerSingleton(){}public static StaticInnerSingleton getInstance(){return InnerSingleton.instance;}}思考静态instance不是StaticInnerSingleton类的成员变量所以在类加载的时候不会实例化instance当第一次调用getInstance方法时内部类InnerSingleton类会初始化instanceJVM保证其线程安全性确保该成员变量只初始化一次。既实现了延迟加载又没有性能消耗所以静态内部类这种方式比较推荐。但是有反射和反序列化破坏问题
2.5 枚举
public enum Singleton implements Serializable {INSTANCE;void doSomething(){System.out.println(do something);}}思考枚举可以天然的防止反射和反序列化但是不能延迟加载这种方式是《Effective Java》作者的Josh Bloch提倡的方式。
拓展反射和反序列化破坏单例 反射破坏单例 破坏案例 以DCL为例反射生成俩个对象。 public class Client {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {//获取类的构造器ConstructorLazySingleton constructor LazySingleton.class.getDeclaredConstructor();//设置权限constructor.setAccessible(true);//使用 constructor 创造对象LazySingleton obj1 constructor.newInstance();LazySingleton obj2 constructor.newInstance();System.out.println(obj1);System.out.println(obj2);}
}运行结果 打印俩次对象的地址不一样说明俩个对象不是同一个。 预防措施 可以在构造方法中抛出异常 public class LazySingleton implements Serializable {//volatile 防止指令重排private static volatile LazySingleton singleton;private LazySingleton(){if(singleton!null){throw new RuntimeException(不允许重复创建对象);}}}枚举预防源码 newInstance方法单独判断是否是枚举类型如果是的话抛出异常防止反射破坏单例模式。 反序列化破坏单例 破坏案例 以DCL为例反序列化生成俩个对象。 public class Client2 {public static void main(String[] args) throws Exception {LazySingleton hungrySingleton LazySingleton.getInstance();System.out.println(hungrySingleton);//将得到的实例序列化到磁盘FileOutputStream fileOutputStream new FileOutputStream(D:/LazySingleton.txt);ObjectOutputStream objectOutputStream new ObjectOutputStream(fileOutputStream);objectOutputStream.writeObject(hungrySingleton);objectOutputStream.flush();objectOutputStream.close();//从磁盘反序列化得到实例FileInputStream fileInputStream new FileInputStream(D:/LazySingleton.txt);ObjectInputStream objectInputStream new ObjectInputStream(fileInputStream);LazySingleton singleton (LazySingleton) objectInputStream.readObject();System.out.println(singleton);}
}运行结果 打印俩次对象的地址不一样说明俩个对象不是同一个。 原因分析 obj desc.isInstantiable() ? desc.newInstance() : null; 这行代码的意思是:如果这个类可以序列化就创建新对象不行就返回null。 预防措施 添加readResolve()返回单例对象。当反序列化恢复一个新对象时系统会自动调用这个readResolve()方法返回指定好的对象。 public class LazySingleton implements Serializable {//volatile 防止指令重排private static volatile LazySingleton singleton;private Object readResolve(){return singleton;}
}枚举预防源码 readEnum方法中‘Enum? en Enum.valueOf((Class)cl, name);’这行代码等提供于将instance对象赋值给en枚举做了特殊处理防止反序列化破坏单例模式。
3.定义以及实现步骤
单例模式Singleton指一个类只有一个实例且该类能自行创建这个实例的一种模式。 通用实现步骤
私有化构造方法在单例内部创建一个唯一实例提供一个外部获取实例的方法
4.优缺点以及应用场景
优点
提供了唯一实例的全局访问方法可以优化共享资源的访问避免对象的频繁创建和销毁可以提高性能
缺点:
单例模式的代码基本上在一个类中违反了单一职责单例模式不易扩展扩展需要修改原来的代码违背开闭原则
适用场景
创建一个对象资源消耗过高并且只需一个只允许使用一个公共访问点
现实应用场景
数据库连接池手机app窗口大多数appSpring中Bean的默认生命周期Windows任务管理器
你的收藏和点赞就是我最大的创作动力关注我我会持续输出更新
友情提示请尊重作者劳动成果如需转载本博客文章请注明出处谢谢合作
【作者我爱吃菠菜 】