John Squirrels
第 41 级
San Francisco

Java单例类

已在 随机的 群组中发布
个会员
你好!今天,我们将从 Java 单例模式开始,深入探讨各种设计模式的细节。让我们回顾一下:我们对设计模式一般了解多少?设计模式是我们可以用来解决许多已知问题的最佳实践。设计模式通常不依赖于任何编程语言。将它们视为一组建议,以帮助您避免错误并避免重新发明轮子。设计模式:单例 - 1

Java中的单例是什么?

单例模式是最简单的类级设计模式之一。有时人们说“这个类是单例的”,这意味着该类实现了单例设计模式。有时需要编写一个类,我们将实例化限制为单个对象。例如,一个类负责记录或连接到一个数据库。单例设计模式描述了我们如何实现这一点。单例是一种做两件事的设计模式:
  1. 它保证永远只有一个类的实例。

  2. 它提供对该实例的单点全局访问。

因此,几乎每个单例模式的实现都有两个特征:
  1. 私有构造函数。这限制了在类本身之外创建类对象的能力。

  2. 返回类实例的公共静态方法。此方法称为getInstance。这是对类实例的全局访问点。

实施方案

单例设计模式以各种方式应用。每个选项都以其自己的方式是好的和坏的。一如既往,这里没有完美的选择,但我们应该争取一个。首先,让我们决定好与坏的构成,以及影响我们如何评估设计模式的各种实现的指标。让我们从好的开始。以下是使实施更具吸引力和吸引力的因素:
  • 惰性初始化:直到需要时才创建实例。

  • 简单透明的代码:这个指标当然是主观的,但它很重要。

  • 线程安全:在多线程环境下正确运行。

  • 多线程环境中的高性能:共享资源时很少或没有线程阻塞。

现在的缺点。我们将列出影响实施的因素:
  • 没有惰性初始化:当应用程序启动时加载类,无论是否需要它(矛盾的是,在 IT 世界中,惰性更好)

  • 复杂且难以阅读的代码。这个指标也是主观的。如果您的眼睛开始流血,我们会假设实施不是最好的。

  • 缺乏线程安全。换句话说,“线程危险”。多线程环境下的错误操作。

  • 多线程环境下的性能不佳:线程在共享资源时一直或经常相互阻塞。

代码

现在我们准备好考虑各种实施方案并指出优缺点:

简单的

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
最简单的实现。优点:
  • 简单透明的代码

  • 线程安全

  • 多线程环境下的高性能

缺点:
  • 没有惰性初始化。
为了修复前面的缺点,我们得到了第二个实现:

延迟初始化

public class Singleton {
  private static final Singleton INSTANCE;

  private Singleton() {}

  public static Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
优点:
  • 延迟初始化。

缺点:
  • 不是线程安全的

这个实现很有趣。我们可以延迟初始化,但是我们失去了线程安全。不用担心——我们在第三个实现中同步所有内容。

同步访问

public class Singleton {
  private static final Singleton INSTANCE;

  private Singleton() {
  }

  public static synchronized Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
优点:
  • 延迟初始化。

  • 线程安全

缺点:
  • 多线程性能差

出色的!在第三个实现中,我们恢复了线程安全!当然慢了。。。现在getInstance方法是synchronized的,所以一次只能一个线程执行。我们实际上只需要同步初始化新实例的部分,而不是同步整个方法。但是我们不能简单地使用同步块来包装负责创建新实例的部分。这样做并不能确保线程安全。这一切都有点复杂。正确的同步可以在下面看到:

双重检查锁定

public class Singleton {
    private static final Singleton INSTANCE;

  private Singleton() {
  }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}
优点:
  • 延迟初始化。

  • 线程安全

  • 多线程环境下的高性能

缺点:
  • Java 1.5 以下的早期版本不支持(从 1.5 版本开始固定使用 volatile 关键字)

请注意,为了使此实施选项正常工作,必须满足两个条件之一。INSTANCE变量必须是finalvolatile 我们今天要讨论的最后一个实现是类持有者单例

班级持有人

public class Singleton {

   private Singleton() {
   }

   private static class SingletonHolder {
       public static final Singleton HOLDER_INSTANCE = new Singleton();
   }

   public static Singleton getInstance() {
       return SingletonHolder.HOLDER_INSTANCE;
   }
}
优点:
  • 延迟初始化。

  • 线程安全。

  • 多线程环境下的高性能。

缺点:
  • 正确的操作需要保证单例对象的初始化没有错误。否则,对getInstance方法的第一次调用将导致ExceptionInInitializerError,所有后续调用将产生NoClassDefFoundError

这个实现几乎是完美的。它是惰性的,线程安全的,而且速度很快。但它有细微差别,如缺点列表中所述。 单例模式的各种实现比较:
执行 延迟初始化 线程安全 多线程性能 什么时候使用?
简单的 - + 快速地 绝不。或者可能在延迟初始化不重要时。但永远不会更好。
延迟初始化 + - 不适用 总是在不需要多线程时
同步访问 + + 慢的 绝不。或者可能在多线程性能无关紧要时。但永远不会更好。
双重检查锁定 + + 快速地 在极少数情况下,当您需要在创建单例时处理异常(当类持有者单例不适用时)
班级持有人 + + 快速地 每当需要多线程并且可以保证创建单例对象时不会出现问题。

单例模式的优缺点

通常,单身人士会完全按照预期进行操作:
  1. 它保证永远只有一个类的实例。

  2. 它提供对该实例的单点全局访问。

但是,这种模式有缺点:
  1. 单例违反了单一职责原则:单例类除了直接的职责外,还控制着实例的数量。

  2. 普通类对单例的依赖在类的公共契约中是不可见的。

  3. 全局变量不好。最终,单例会变成一个庞大的全局变量。

  4. 单例的存在降低了整个应用程序的可测试性,尤其是使用单例的类。

就是这样!:) 我们已经与您探讨了 Java 单例类。现在,在你的余生中,当与你的程序员朋友交谈时,你不仅可以提及模式有多好,还可以说几句它的坏处。祝你好运掌握这些新知识。

补充阅读:

评论
  • 受欢迎
你必须先登录才能发表评论
此页面还没有任何评论