本文在Yeliheng的技术小站同步发布
当你想在一个庞大的工程中,确保整个程序只存在一个唯一的实例,并且保证在任何情况下,新的实例都不会被创建,这时候,单例模式将是你的不二之选。
我们在使用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语句来判断,这两个实例是否是同一个实例。代码十分简单,我们一起来看看运行结果。
运行结果如下:
显而易见,两个实例是同一个实例,我们的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关键字,并在静态成员方法处增加一个同步锁,就可以完美解决这个问题,保证线程安全。这样,全局程序将不可能出现多个实例。
总结单例模式是设计模式中常用的模式之一。单例模式的形式常分为饿汉式和懒汉式。饿汉式的优点是创建起来简单,并且天然线程安全。缺点是在大量单例存在时,将造成资源浪费。懒汉式的优点是性能较好,按需加载对象,能够让内存空间被较佳利用。缺点是创建相对于饿汉式复杂,在多线程的场景下,对多线程不熟悉或稍不注意容易出现线程安全问题。