网站网站制作多少钱,网站推广产品怎么做,什么是 网站收录,正规网站建设空间哪个好为什么有单例模式#xff1f; 
单例模式#xff08;Singleton#xff09;#xff0c;也叫单子模式#xff0c;是一种常用的软件设计模式。在应用这个模式时#xff0c;单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象#xff0c;这样有利…为什么有单例模式 
单例模式Singleton也叫单子模式是一种常用的软件设计模式。在应用这个模式时单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象这样有利于我们协调系统整体的行为。 实现原理是什么 
构造方法是privatestatic方法if语句判断 注意不同的实现方式它的实现原理肯定是有所区别的综合来看 实现方式有哪些 
懒汉式、双重锁、饿汉式、静态内部类、枚举 懒汉式 
好处启动速度快、节省资源一直到实例被第一次访问才需要初始化单例、避免空间浪费缺点线程不安全if语句存在竞态条件  
单例类 
package com.example;/*** BelongsProject: BigK* BelongsPackage: com.example* Author: dengLiMei* CreateTime: 2023-06-28  10:04* Description: 单例模式* Version: 1.0*/
public class Singleton {//提供一个全局变量让全局访问private static Singleton instance;//私有构造方法堵死外界利用new创建此类实例的可能private Singleton() {}//获得实例的唯一全局访问点public static Singleton GetInstance() {//当多线程来临的时候判断是否为null此时instance就是临界资源会实例化多个if (instance  null) {instance  new Singleton();}return instance;}
}客户端 
//反射破坏封装性
Singleton instance1  Singleton.GetInstance();// 使用反射获取私有构造函数
ConstructorSingleton constructor  Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);// 通过反射创建第二个实例
Singleton instance2  constructor.newInstance();System.out.println(instance1); // 输出第一个实例的内存地址
System.out.println(instance2); // 输出第二个实例的内存地址这里我是通过反射的方式去获取对象然后对获取到的对象进行判断运行代码之后我们会发现  两个对象的内存地址并不相同违背了单一性那我们如何解决这个问题呢可能屏幕前有些小伙伴想到了加锁的方式去做没错我们用大家比较常见的synchronized实现看看吧。 懒汉式变种-synchronized 
好处线程安全缺点并发性能差synchronized加锁不管有没有对象都加锁 单例类 
package com.example;/*** BelongsProject: BigK* BelongsPackage: com.example* Author: dengLiMei* CreateTime: 2023-06-28  10:14* Description: 懒汉单例在第一次被引用时才会将自己实例化* Version: 1.0*/
public class LazySingleton {private static LazySingleton instance;private LazySingleton() {System.out.println(创建一次);}public static LazySingleton GetInstance() {//方法一加锁-把判断的这部分逻辑上锁//好处线程安全//缺点并发性能差synchronized加锁不管有没有对象都加锁//解决方案双重锁synchronized () {if (instance  null) {instance  new LazySingleton();}}return instance;}//方法二同步代码段public static synchronized LazySingleton getSingleton() {if (instance  null) {instance  new LazySingleton();}return instance;}
} 
客户端 //懒汉模式加锁保证线程安全
Runnable r3  () - {DoubleLockSingleton s1  DoubleLockSingleton.GetInstance();
DoubleLockSingleton s2  DoubleLockSingleton.GetInstance();if (s1  s2) {System.out.println(两个对象是相同的实例);
}
};Thread t1  new Thread(r3);
Thread t2  new Thread(r3);t1.start();
t2.start();通过运行结果我们会发现两个线程获取到的对象是同一个实现了单例。 但是大家可以思考一下这样会不会存在什么问题呢线程因为每次访问 getInstance() 方法时都需要获取锁即使实例已经被创建会在高并发环境下其实是比较影响性能的。并且会导致每次调用 getInstance() 方法都需要获取锁而不是在需要时才创建实例。那我们可不可以当单例对象没有被创建的时候才去加锁呢双重锁可以做到  
懒汉式变种-双重锁 
好处实现线程安全地创建实例而又不会对性能造成太大影响。缺点无效等待同步效率地锁占用资源反射会破坏单一性 单例类 
package com.example;/*** BelongsProject: BigK* BelongsPackage: com.example* Description: 懒汉单例——双重锁* Version: 1.0*/
public class DoubleLockSingleton {//volatile禁止指令重排序防止部分初始化private static volatile DoubleLockSingleton instance;private DoubleLockSingleton() {System.out.println(实例化了一次);}//原理双重if延迟实例化避免每次进行同步的性能开销public static DoubleLockSingleton GetInstance() {//第一层判断先判断实例是否存在不存在再加锁处理if (instance  null) {synchronized () {//第二层判断if (instance  null) {instance  new DoubleLockSingleton();}}}return instance;}
} 
客户端 
DoubleLockSingleton instance1  DoubleLockSingleton.GetInstance();// 使用反射获取私有构造函数
ConstructorDoubleLockSingleton constructor  DoubleLockSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);// 通过反射创建第二个实例
DoubleLockSingleton instance2  constructor.newInstance();System.out.println(instance1); // 输出第一个实例的内存地址
System.out.println(instance2); // 输出第二个实例的内存地址这里我们依旧使用反射去获取单例对象。我们运行看看效果  发现构造方法被调用了两地并且获取到的两个对象的地址也不同依旧是破坏了单例性。 双重锁实现方式是在第一次创建实例的时候同步以后就不需要同步了。反射的使用让我们的单例类又不攻自破没关系咱们还有其他方式——饿汉式 饿汉式 
优点类加载阶段创建保证了线程安全缺点可能存在没有被使用的可能造成资源浪费  
单例类 
package com.example;/*** 饿汉模式类加载时初始化单例以后访问时直接返回即可*/
public class HungrySingleton {//类加载阶段就实例化private static final HungrySingleton singleton  new HungrySingleton();private HungrySingleton() {}public static HungrySingleton getInstance() {return singleton;}
} 
客户端 
//获取单例对象
HungrySingleton singleton  HungrySingleton.getInstance();// 使用反射获取单例对象
try {Class? singletonClass  Class.forName(com.example.HungrySingleton);// 获取私有构造函数Constructor? constructor  singletonClass.getDeclaredConstructor();constructor.setAccessible(true);// 通过反射实例化对象HungrySingleton singletonReflection  (HungrySingleton) constructor.newInstance();// 验证是否为同一对象System.out.println(singleton  singletonReflection);  // 输出 true
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {e.printStackTrace();
}使用反射获取单例对象我们看下输出结果  在整个应用程序的生命周期中无论是否会用到该单例实例都会在类加载时创建实例可能会导致资源的浪费。饿汉模式无法实现延迟加载即在需要时才创建实例。这可能会导致在应用程序启动时就创建了大量的实例占用内存。 基于这些原因尽管饿汉模式是一种简单且线程安全的单例模式实现方式但在资源利用、延迟加载和异常处理等方面存在一些问题。所以我们在实际使用过程中需要根据具体场景选择合适的单例模式实现方式 静态内部类 
好处 
懒加载静态内部类的方式能够实现懒加载即在需要时才会加载内部类从而创建单例对象。这样可以避免在类加载时就创建单例对象节省了资源。线程安全静态内部类的方式利用了类加载机制和静态变量的特性能够保证在多线程环境下也能够保持单例的唯一性而且不需要使用同步关键字。延迟加载由于静态内部类的加载是在需要时才进行的因此能够实现延迟加载即在第一次使用时才会创建单例对象。 
缺点静态内部类的方式需要额外的类加载和内存开销因为它需要创建一个内部类对象而内部类对象的创建需要额外的内存开销。  
单例类 
package com.example;/*** 静态内部类* */
public class StaticInnerSingleton {//静态内部类private static class SingletonHolder {private static final StaticInnerSingleton INSTANCE  new StaticInnerSingleton();}private StaticInnerSingleton (){}public static final StaticInnerSingleton getInstance() {return SingletonHolder.INSTANCE;}}客户端 
//获取单例对象
StaticInnerSingleton singleton  StaticInnerSingleton.getInstance();// 使用反射获取单例对象
try {Class? singletonClass  Class.forName(com.example.StaticInnerSingleton);// 获取私有构造函数Constructor? constructor  singletonClass.getDeclaredConstructor();constructor.setAccessible(true);// 通过反射实例化对象StaticInnerSingleton singletonReflection  (StaticInnerSingleton) constructor.newInstance();// 验证是否为同一对象System.out.println(singleton  singletonReflection);  // 输出 true
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {e.printStackTrace();
}我们来看看运行结果  获取的两个对象的地址是不相同的实现了单例。 它利用了类加载的特性和静态内部类的懒加载特性解决了饿汉模式的资源浪费和懒汉模式的线程安全问题。具体实现方式是在外部类中定义一个私有的静态内部类内部类中创建单例实例并且利用类加载的特性保证了实例的唯一性。同时由于静态内部类是在需要的时候才加载因此实现了延迟加载的效果。也是比较推荐的一种方式 枚举 
优点线程安全、防止反序列化重新创建新的对象 单例类 
package com.example;/*** 枚举方式*/
public enum EnumSingleton {INSTANCE;
} 
客户端 // 获取单例对象
EnumSingleton singleton1  EnumSingleton.INSTANCE;
EnumSingleton singleton2  EnumSingleton.INSTANCE;// 验证是否为同一对象
System.out.println(singleton1  singleton2);  // 输出 true我们来让控制台打印输出看看结果  在Java中枚举类型是线程安全的并且保证在任何情况下都是单例的。因此使用枚举实现单例模式是一种推荐的方式。具体实现方式是定义一个包含单个枚举常量的枚举类型这个枚举常量就是单例实例。由于枚举类型在Java中是天然的单例因此不需要担心线程安全和反射攻击等问题。 使用场景有哪些 
Windows的Task Manager任务管理器、回收站 使用时如何选择 在实际业务场景中可以根据具体需求选择适合的单例模式。如果需要在应用启动时创建对象且对性能要求较高可以选择饿汉式或双重校验锁如果需要延迟加载对象可以选择静态内部类或枚举单例模式如果对线程安全要求较高可以选择双重校验锁或静态内部类单例模式 如果有想要交流的内容欢迎在评论区进行留言如果这篇文档受到了您的喜欢那就留下你点赞收藏评论脚印支持一下博主~