Java设计模式再相识 (一)——单例模式

191 阅读4分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

当你想在一个庞大的工程中,确保整个程序只存在一个唯一的实例,并且保证在任何情况下,新的实例都不会被创建,这时候,单例模式将是你的不二之选。

我们在使用Windows操作系统时,是否曾注意到任务管理器,回收站等系统应用始终只能打开一个窗口,这就是单例模式的类比。接下来,我们将动手实现一个单例模式的程序。在开始之前,我们先来熟悉单例模式的基本概念。

基础篇

介绍

单例(Singleton)模式的定义:一个类只允许拥有一个实例。

单例模式通常有两种实现形式:饿汉式,懒汉式。在基础篇中,我将详细介绍饿汉式单例模式。

何为饿汉式?顾名思义,就是在类加载时就创建一个单例,这样能够保证在调用getInstance()方法之前这个单例就已经存在。

现在,就让我们来编写代码模拟实现一个Windows任务管理器,让它只能存在一个。

示例-模拟任务管理器

我们新建一个名为Singleton.java的文件,代码内容如下:

Singleton.java

package com.yeliheng.singleton;

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

    private Singleton() {
        System.out.println("任务管理器已打开!");
    }

    public static Singleton getInstance() {
        return singleton;
    }
}

在Singleton类中,我们定义了static字段的成员变量singleton,并将其初始化为Singleton类的实例。然后我们定义了一个私有构造函数Singleton,打印提示。使用private修饰符可以防止从Singleton类以外的代码调用构造函数,从而单例模式将失去意义。

接着我们定义getInstance()方法,就可以让外部程序以只读的方式获取到Singleton类的唯一实例。(通常情况下,我们采用getInstance作为方法名。)

Main.java


package com.yeliheng.singleton;

public class Main {

    public static void main(String[] args) {

        Singleton obj1 = Singleton.getInstance();

        Singleton obj2 = Singleton.getInstance();

        if(obj1.equals(obj2)) {
            System.out.println("obj1和obj2是同一个实例,任务管理器只能创建一个!");
        }
    }
}

在Main函数中,我们定义obj1和obj2两个变量,这两个变量都去创建两次Singleton实例。然后我们使用if语句来判断,这两个实例是否是同一个实例。代码十分简单,我们一起来看看运行结果。

运行结果如下:

QQ20220210-214108@2x.png

显而易见,两个实例是同一个实例,我们的Singleton只被创建了一次。

饿汉式单例我们就介绍到这里,接下来,我们开始介绍懒汉式单例。

进阶篇

懒汉式单例

懒汉式单例在类加载时不会生成单例,只有当第一次调用getInstance()方法时才去创建这个单例。代码如下:

package com.yeliheng.singleton;

public class LazySingleton {
    
    private static LazySingleton lazySingleton = null; //创建一个空的懒汉式单例

    private LazySingleton() {

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

在这个例子中,代码与前面的大同小异,就是在初始化lazySingleton变量时,没有像饿汉式单例那样立即实例化,而是指定一个null,当需要获取Instance实例时,再进行实例化。这样做,可以在大型计算场景中,节省内存,按需加载。

不知道聪明的你有没有注意到这样做的问题。在上面这个代码中,存在线程安全问题。在多线程程序的条件下,会出现LazySingleton还未创建完成的情况下,另一个线程又去访问调用LazySignleton,造成实例被创建多次的情况,这样它就不再是一个单例模式了。我们现在对程序进行改进:


package com.yeliheng.singleton;

public class LazySingleton {

    private static volatile LazySingleton lazySingleton = null; //创建一个空的懒汉式单例

    private LazySingleton() {

    }

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

没错,只需要在变量出增加volatile关键字,并在静态成员方法处增加一个同步锁,就可以完美解决这个问题,保证线程安全。这样,全局程序将不可能出现多个实例。

总结

单例模式是设计模式中常用的模式之一。单例模式的形式常分为饿汉式和懒汉式。饿汉式的优点是创建起来简单,并且天然线程安全。缺点是在大量单例存在时,将造成资源浪费。懒汉式的优点是性能较好,按需加载对象,能够让内存空间被较佳利用。缺点是创建相对于饿汉式复杂,在多线程的场景下,对多线程不熟悉或稍不注意容易出现线程安全问题。

源码参考:GitHub