计划模式(创建型)-单例模式

打印 上一主题 下一主题

主题 939|帖子 939|积分 2817

择要        

        在软件开辟的世界里,计划模式是开辟者们智慧的结晶,它们为解决常见问题提供了颠末验证的通用方案。单例模式作为一种基础且常用的计划模式,在许多场景中发挥着关键作用。本文将深入探讨单例模式的定义、实现方式、应用场景以及大概面临的问题与解决方案。
定义

        单例模式的核心目标是保证一个类在整个体系中仅有一个实例存在,而且为体系提供一个访问该实例的全局访问点。这种模式在资源管理、数据共享等方面具有紧张意义。比方,在一个数据库连接管理体系中,为了克制频繁创建和销毁数据库连接带来的性能开销,使用单例模式确保整个应用程序只有一个数据库连接实例,所有对数据库的操纵都通过这个唯一的连接举行。
类图


实现方式

饿汉式

        饿汉式单例在类加载时就立即创建唯一的实例对象。代码实现如下:
  1. public class HungrySingleton {
  2.     // 声明并初始化唯一实例
  3.     private static final HungrySingleton instance = new HungrySingleton();
  4.     // 私有构造函数,防止外部实例化
  5.     private HungrySingleton() {}
  6.     // 提供全局访问点
  7.     public static HungrySingleton getInstance() {
  8.         return instance;
  9.     }
  10. }
复制代码
        这种方式的优点是线程安全,因为实例在类加载阶段就已创建,而类加载过程由 JVM 保证线程安全。同时,调用服从高,因为不必要额外的同步操纵。然而,它的缺点是不能延迟加载。假如该单例对象占用资源较大,而在体系运行过程中大概很长时间都不会用到,那么这种提前创建实例的方式会造成资源浪费。
懒汉式

        懒汉式单例是在第一次调用 getInstance 方法时才创建实例。代码如下:
  1. public class LazySingleton {
  2.     // 声明静态实例,初始值为null
  3.     private static volatile LazySingleton instance = null;
  4.     // 私有构造函数
  5.     private LazySingleton() {}
  6.     // 同步的获取实例方法
  7.     public static synchronized LazySingleton getInstance() {
  8.         if (instance == null) {
  9.             instance = new LazySingleton();
  10.         }
  11.         return instance;
  12.     }
  13. }
复制代码
        懒汉式的优势在于可以延迟加载,只有在真正必要使用实例时才创建,克制了资源的过早占用。但是,由于 getInstance 方法使用了 synchronized 关键字举行同步,在多线程环境下,每次调用该方法都必要举行同步操纵,这会导致调用服从不高。
双重查抄锁式

    双重查抄锁模式旨在解决单例、性能和线程安全问题。代码如下:
  1. public class LazyMan3 {
  2.     private LazyMan3(){}
  3.     private static volatile LazyMan3 instance;
  4.     public static LazyMan3 getInstance(){
  5.         //第一次判断,如果instance不为null,不需要抢占锁,直接返回对象
  6.         if (instance == null){
  7.             synchronized (LazyMan3.class){
  8.                 //第二次判断
  9.                 if (instance == null){
  10.                     instance = new LazyMan3();
  11.                 }
  12.             }
  13.         }
  14.         return instance;
  15.     }
  16. }
  17. class LazyMan3Test{
  18.     public static void main(String[] args) {
  19.         LazyMan3 instance = LazyMan3.getInstance();
  20.         LazyMan3 instance1 = LazyMan3.getInstance();
  21.         System.out.println(instance == instance1);
  22.     }
  23. }
复制代码
        这种方式通过两次 if 判断,在第一次判断实例不为 null 时,直接返回实例,克制了同步操纵带来的性能开销。只有当实例为 null 时,才进入同步块举行实例创建。然而,在多线程环境下,由于 JVM 会对实例化对象举行优化和指令重排序操纵,大概会出现空指针问题。解决这个问题的方法是使用 volatile 关键字修饰 instance 变量,volatile 可以保证可见性和有序性,防止指令重排序导致的空指针非常。
静态内部类式

        静态内部类式单例使用了类加载机制来实现线程安全和延迟加载。代码如下:
  1. public class LazyMan4 {
  2.     private LazyMan4(){}
  3.     //定义一个静态内部类
  4.     private static class LazyMan4Holder{
  5.         private static final LazyMan4 INSYANCE = new LazyMan4();
  6.     }
  7.     //对外访问方法
  8.     public static LazyMan4 getInstance(){
  9.         return LazyMan4Holder.INSYANCE;
  10.     }
  11. }
  12. class LazyMan4Test{
  13.     public static void main(String[] args) {
  14.         LazyMan4 instance = LazyMan4.getInstance();
  15.         LazyMan4 instance1 = LazyMan4.getInstance();
  16.         System.out.println(instance == instance1);
  17.     }
  18. }
复制代码
        当外部类 LazyMan4 被加载时,其静态内部类 LazyMan4Holder 并不会立即被加载。只有当调用 getInstance 方法时,LazyMan4Holder 才会被加载,此时会创建 LazyMan4 的唯一实例。这种方式保证了线程安全,因为类加载过程是线程安全的。同时,实现了延迟加载,提高了资源的使用服从。不过,与懒汉式类似,其调用服从相对不高。
静态代码块式

        静态代码块式单例在静态代码块中完成实例的初始化。代码如下:
  1. public class HungryChinese2 {
  2.     //私有构造方法,为了不让外界创建该类的对象
  3.     private HungryChinese2(){}
  4.     //声明该类类型的变量
  5.     private static HungryChinese2 hungryChinese2;//初始值为null
  6.     //静态代码块中赋值
  7.     static {
  8.         hungryChinese2 = new HungryChinese2();
  9.     }
  10.     //对外提供的访问方式
  11.     public static HungryChinese2 getInstance(){
  12.         return hungryChinese2;
  13.     }
  14. }
  15. class HungryChinese2Test{
  16.     public static void main(String[] args) {
  17.         HungryChinese2 instance = HungryChinese2.getInstance();
  18.         HungryChinese2 instance1 = HungryChinese2.getInstance();
  19.         System.out.println(instance.equals(instance1));
  20.     }
  21. }
复制代码
        这种方式与饿汉式类似,在类加载时通过静态代码块创建实例,因此线程安全,但不能延迟加载。
罗列式

        罗列式单例是一种简便且强大的实现方式。代码如下:
  1. public enum LazyMan5 {
  2.     INSTANCE;
  3. }
  4. class LazyMan5Test{
  5.     public static void main(String[] args) {
  6.         LazyMan5 instance = LazyMan5.INSTANCE;
  7.         LazyMan5 instance1 = LazyMan5.INSTANCE;
  8.         System.out.println(instance == instance1);
  9.     }
  10. }
复制代码
        使用罗列实现单例,不光线程安全,调用服从高,而且天然地防止了反射和反序列化漏洞。在反序列化时,罗列范例会保证返回的是已有的罗列常量,而不会创建新的对象。不过,它同样不能延迟加载。
应用场景


  • 资源管理:如数据库连接池、线程池等资源,使用单例模式可以确保整个体系中只有一个资源实例,克制资源的重复创建和浪费,提高资源的使用率和管理服从。
  • 全局配置:体系的全局配置信息,如体系参数、环境变量等,使用单例模式可以方便地在整个体系中访问和修改这些配置,保证配置的一致性。
  • 日志记录:日志记录器通常使用单例模式,以便在整个应用程序中记录日志信息。所有的日志记录操纵都通过同一个日志记录器实例举行,方便管理和维护日志文件。
  • 缓存管理:缓存体系可以使用单例模式来管理缓存实例,确保不同模块对缓存的访问和操纵是一致的,提高缓存的命中率和性能。
单例模式大概面临的问题及解决方案

Serializable问题



  • 假如单例类实现了 java.io.Serializable 接口,在反序列化时大概会出现问题。因为反序列化过程会创建一个新的对象,这大概导致多次反序列化同一对象时得到多个单例类的实例,破坏了单例模式的唯一性。解决方法是在单例类中添加 readResolve 方法:
  1. private Object readResolve() throws ObjectStreamException {
  2.     return instance;
  3. }
复制代码
        这样,在反序列化时,假如定义了 readResolve 方法,则直接返回此方法指定的对象,而不会创建新的对象,从而保证了单例的唯一性。
在Android 中使用单例模式大概会内存泄漏

        在 Android 开辟中,当单例类依靠于 Context 时,假如传入的是 Activity 的 Context,大概会导致内存泄漏。比方:
  1. public class CommUtils {
  2.     private volatile static CommUtils mCommUtils;
  3.     private Context mContext;
  4.     public CommUtils(Context context) {
  5.         mContext=context;
  6.     }
  7.     public static  CommUtils getInstance(Context context) {
  8.         if (mCommUtils == null) {
  9.             synchronized (CommUtils.class) {
  10.                 if (mCommUtils == null) {
  11.                     mCommUtils = new CommUtils(context);
  12.                 }
  13.             }
  14.         }
  15.         return mCommUtils;
  16.     }
  17. }
复制代码
        只要这个单例没有被释放,那么持有该单例的 Activity 也不会被释放,直到进程退出。为了解决这个问题,应尽量使用 Application 的 Context,因为 Application 的生命周期伴随着整个进程的周期,不会因为某个 Activity 的销毁而导致单例持有无效的 Context,从而克制内存泄漏。
总结

        单例模式在软件开辟中具有广泛的应用,不同的实现方式各有优劣。开辟者必要根据具体的需求和场景,选择符合的单例实现方式,同时留意解决大概出现的问题,以确保体系的高效、稳固运行。通过公道运用单例模式,可以提高代码的可维护性、可扩展性和性能,为软件项目的成功开辟奠定坚实的基础。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

风雨同行

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表