Java 单例设计模式示例

x33g5p2x  于2021-10-16 转载在 Java  
字(3.6k)|赞(0)|评价(0)|浏览(149)

当您希望给定类的只有一个实例 时,可以使用单例设计模式。

它是一种创建设计模式,我们在其中处理对象的创建。

动机和现实世界的例子

在面向对象设计中,一些类只有一个实例是非常重要的。那是因为它们代表了某种独特的东西,某种独一无二的东西。

让我们看一些 Java 语言中单例的真实示例,以了解这意味着什么 -

1.java.lang.Runtime:Java 提供了一个 Runtime 类,表示应用程序在其中运行的当前运行时环境。应用程序可以使用此类与其运行时环境进行交互。

由于 Runtime 环境是唯一的,该类应该只有一个实例。
1.java.awt.Desktop:Desktop 类允许 Java 应用程序使用在本地桌面(如用户的默认浏览器或邮件客户端)上注册的应用程序启动 URI 或文件。

本机桌面和相关应用程序是独一无二的。所以必须只有一个 Desktop 类的实例。

实现单例设计模式

你如何确保一个类只有一个实例?嗯,在 Java 中有几种方法可以做到这一点。但所有这些都基于以下基本思想:

声明一个私有构造函数以防止其他人实例化该类。
1.
在静态字段/块中的类加载期间,或在静态方法中按需创建类的实例,该方法首先检查实例是否存在并仅在不存在时创建新实例。

让我们通过代码示例一一看看所有可能的解决方案:

1. 急切地初始化单例

这是最简单的方法,其中在类加载时创建类的实例 -

public class EagerSingleton {

    /** private constructor to prevent others from instantiating this class */
    private EagerSingleton() {}

    /** Create an instance of the class at the time of class loading */
    private static final EagerSingleton instance = new EagerSingleton();

    /** Provide a global point of access to the instance */
    public static EagerSingleton getInstance() {
        return instance;
    }
}

这种方法的缺点是实例的创建与它是否被访问无关。如果对象很简单并且不包含任何系统资源,这很好。但是,如果它分配大量系统资源并保持未使用状态,则会对性能产生影响。

2. 急切地初始化静态块单例

您还可以在静态块中创建该类的一次性实例。这是有效的,因为静态块在类加载时只执行一次。

静态块初始化的优点是您可以在静态块中编写初始化逻辑或处理异常。

public class EagerStaticBlockSingleton {

    private static final EagerStaticBlockSingleton instance;

    /** Don't let anyone else instantiate this class */
    private EagerStaticBlockSingleton() {}

    /** Create the one-and-only instance in a static block */
    static {
        try {
            instance = new EagerStaticBlockSingleton();
        } catch (Exception ex) {
            throw ex;
        }
    }

    /** Provide a public method to get the instance that we created */
    public static EagerStaticBlockSingleton getInstance() {
        return instance;
    }
}

就像之前的解决方案一样,无论应用程序是否需要,都会创建实例。

3. 延迟初始化的单例

延迟初始化意味着延迟初始化,直到第一次需要它。

在下面的实现中,我们首先在 getInstance() 方法中检查实例是否已经创建。如果实例已经创建,我们只需返回它,否则,我们首先创建实例然后返回它:

public class LazySingleton {

    private static LazySingleton instance;

    /** Don't let anyone else instantiate this class */
    private LazySingleton() {}

    /** Lazily create the instance when it is accessed for the first time */
    public static synchronized LazySingleton getInstance() {
        if(instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

注意 synchronized 关键字在 getInstance() 方法中的使用。这是在多线程环境中防止竞争条件所必需的。

假设 instance 尚未创建,两个线程同时进入 getInstance() 方法。在这种情况下, instance==null 检查将评估为 true,并且两个线程都将创建该类的新实例。

synchronized 关键字确保一次只有一个线程可以执行 getInstance() 方法。

4. 延迟初始化的双重检查锁定单例

添加到 getInstance() 方法中的 synchronized 关键字可以防止竞争条件,但也会导致一些性能损失。

以下是延迟初始化单例的优化版本,其中 - 我们创建了一个 synchronized 块并仅将实例化部分包装在 synchronized 块中,而不是使整个方法 synchronized ——

public class LazyDoubleCheckedLockingSingleton {

    private static volatile LazyDoubleCheckedLockingSingleton instance;

    /** private constructor to prevent others from instantiating this class */
    private LazyDoubleCheckedLockingSingleton() {}

    /** Lazily initialize the singleton in a synchronized block */
    public static LazyDoubleCheckedLockingSingleton getInstance() {
        if(instance == null) {
            synchronized (LazyDoubleCheckedLockingSingleton.class) {
                // double-check
                if(instance == null) {
                    instance = new LazyDoubleCheckedLockingSingleton();
                }
            }
        }
        return instance;
    }
}

上述方法称为双重检查锁定,因为我们在 synchronized 块内双重检查变量是否已初始化。

双重检查在这里非常重要。假设两个线程 T1T2 同时进入 getInstance() 方法。 instance==null 校验将评估为真,因此它们将一一进入 synchronized 块。如果没有双重检查,两个线程都会创建一个新实例。

另外,请注意将 volatile 关键字与实例变量一起使用。这是必要的,以防止编译器进行自己的优化并正确处理单例。

维基百科对双重检查锁定和 Java 代码有很好的解释。看看这里。

5. 延迟初始化的内部类单例(Bill Pugh 单例)

Bill Pugh 提出了一个非常有效的解决方案来创建单例。它被称为Initialization-on-demand holder idiom。在这种方法中,静态内部类用于延迟创建单例实例。

public class LazyInnerClassSingleton {

    /** private constructor to prevent others from instantiating this class */
    private LazyInnerClassSingleton() {}

    /** This inner class is loaded only after getInstance() is called for the first time. */
    private static class SingletonHelper {
        private static final LazyInnerClassSingleton INSTANCE = new LazyInnerClassSingleton();
    }

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

请注意,直到 getInstance() 方法才加载内部类

相关文章