Java - 单例类

你好,未来的Java法师们!今天,我们将深入探讨Java编程中最吸引人的概念之一:单例类。如果你是编程新手,不用担心;我会一步步引导你,就像我多年来为无数学生所做的那样。所以,拿起你最喜欢的饮料,舒适地坐好,让我们一起踏上这个激动人心的冒险之旅!

Java - Singleton Class

什么是单例类?

想象一下,你是一家非常独特的俱乐部的经理。这个俱乐部如此独特,以至于全世界只能有一个实例。这在Java中基本上就是单例类的定义——一个只允许创建自身一个实例的类。

为什么使用单例?

你可能会想,“为什么我们要限制自己只创建一个实例?”好吧,有几个原因:

  1. 全局访问点:它提供了一个访问特定实例的单一点,使得在应用程序中维护全局状态变得容易。
  2. 延迟初始化:实例只有在需要时才会创建,这样可以节省资源。
  3. 线程安全:当正确实现时,它可以是线程安全的,即使在多线程环境中也只允许一个实例。

现在,让我们看看如何在Java中创建一个单例类。

创建单例类

创建单例类有几种方法,但我们将从最简单且最常见的方法开始:急切初始化。

public class EagerSingleton {
// 类的私有静态实例
private static final EagerSingleton instance = new EagerSingleton();

// 私有构造函数以防止实例化
private EagerSingleton() {}

// 公共方法以返回实例
public static EagerSingleton getInstance() {
return instance;
}
}

让我们分解一下:

  1. 我们声明了一个私有静态最终变量 instance,它是类类型的。这是将要存在的唯一实例。
  2. 构造函数是私有的,防止其他类创建新实例。
  3. 我们提供了一个公共静态方法 getInstance(),它返回单个实例。

要使用这个单例:

EagerSingleton singleton = EagerSingleton.getInstance();

简单吧?但如果我们想要在需要时才创建实例呢?这时就需要延迟初始化。

延迟初始化

public class LazySingleton {
private static LazySingleton instance;

private LazySingleton() {}

public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}

在这个版本中,实例只在第一次调用 getInstance() 时创建。然而,这不是线程安全的。在多线程环境中,我们可能会创建多个实例。让我们来解决这个问题!

线程安全单例

public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;

private ThreadSafeSingleton() {}

public static synchronized ThreadSafeSingleton getInstance() {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
return instance;
}
}

通过在 getInstance() 方法中添加 synchronized 关键字,我们确保只有一个线程可以一次执行这个方法。然而,同步是有代价的,我们只需要在第一次创建实例时使用它。这时,双重检查锁定模式就派上用场了!

双重检查锁定

public class DoubleCheckedSingleton {
private static volatile DoubleCheckedSingleton instance;

private DoubleCheckedSingleton() {}

public static DoubleCheckedSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}

这种模式检查两次null:一次不锁定,一次锁定。volatile 关键字确保多个线程正确处理 instance 变量。

Bill Pugh 单例实现

现在,让我与你分享我个人最喜欢的单例实现方式,它以创造者Bill Pugh的名字命名:

public class BillPughSingleton {
private BillPughSingleton() {}

private static class SingletonHelper {
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}

public static BillPughSingleton getInstance() {
return SingletonHelper.INSTANCE;
}
}

这种方法使用一个静态内部类来持有实例。它是线程安全的,不需要同步,并且当第一次调用 getInstance() 时懒加载实例。

何时使用单例

单例非常适合:

  1. 管理共享资源(如数据库连接)
  2. 协调系统范围内的操作
  3. 管理资源池(如线程池)

然而,要小心!过度使用单例可能会导致代码更难测试和维护。

单例方法

下面是你在单例类中可能会找到的常见方法表:

方法 描述
getInstance() 返回类的单个实例
readResolve() 用于序列化以保持单例属性
clone() 通常抛出 CloneNotSupportedException 以防止克隆

结论

哇!我们今天涵盖了很多内容。从理解单例是什么,到实现各种类型的单例,你现在已经有能力在Java项目中使用这个强大的设计模式了。

记住,就像我们在开头谈论的那个独特的俱乐部一样,单例应该谨慎使用。它是一个强大的工具,但权力越大,责任越大!

在你继续Java之旅时,你将遇到更多令人着迷的概念。继续编码,继续学习,最重要的是,享受乐趣!谁知道呢?也许有一天,你会在教下一代程序员关于单例以及其他更多内容。

下次见,快乐编码!

Credits: Image by storyset