【设计模式】单例模式
一、概念
单例模式是设计模式中最为简单和为人熟知的一种设计模式,属于创建型模式的一种。
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
UML图表示法:
Singleton |
-uniquenstance:Singleton |
-Singleton():void +getInstance():Singletion +operation():void |
图1 单例模式结构图
单例模式具有以下几个特点:
①单例类只能有一个实例
②单例类必须自己创建自己的唯一实例
③单例类必须给所有其他对象提供这一实例
二、示例代码
1、懒汉式:
package com.qunar.base.collection; public class Singleton { /** 定义一个私有的静态变量来存储创建的实例 **/ private static Singleton uniqueInstance = null; /** 私有化构造方法,防止外部类来创建此类的实例 **/ private Singleton() { } /** * 定义一个public的静态方法,向外界提供仅有的这个实例 * * @return */ public static synchronized Singleton getInstance() { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; } /** * 实例方法,外界只能通过仅有的实例来调用此方法 */ public void operate() { } }
2、饿汉式
package com.qunar.base.collection; public class Singleton { /** 定义一个私有的静态变量来存储创建的实例,直接在声明变量时创建此实例 **/ private static Singleton uniqueInstance = new Singleton(); /** 私有化构造方法,防止外部类来创建此类的实例 **/ private Singleton() { } /** * 定义一个public的静态方法,向外界提供仅有的这个实例 * * @return */ public static Singleton getInstance() { // 直接返回已经创建好的实例 return uniqueInstance; } /** * 实例方法,外界只能通过仅有的实例来调用此方法 */ public void operate() { } }
三、模式讲解
1、单例模式的优缺点
a、时间和空间
懒汉式是典型的时间换空间,而饿汉式是典型的空间换时间。
b、线程安全
不加同步的懒汉式是线程不安全的
饿汉式是线程安全的
2、双重检查加锁方式实现的单例模式
代码如下:
package com.qunar.base.collection; public class Singleton { /** 对保存实例的变量添加volatile修饰 **/ private volatile static Singleton uniqueInstance = null; /** 私有化构造方法,防止外部类来创建此类的实例 **/ private Singleton() { } /** * 定义一个public的静态方法,向外界提供仅有的这个实例 * * @return */ public static Singleton getInstance() { // 先检查实例是否存在,如果不存在,才会进入下面的同步块 if (uniqueInstance == null) { // 同步快,线程安全的创建实例 synchronized (Singleton.class) { // 再次检查是否存在,如果不存在才真正的创建实例 if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; } /** * 实例方法,外界只能通过仅有的实例来调用此方法 */ public void operate() { } }
在上面的代码中,如果没有对变量uniqueInstance添加volatile修饰,则可能会产生以下问题:
out-of-order write问题(详见 DoubleCheckedLocking):
instance = new Singleton(); 的顺序应该是
1 分配内存 2 构造函数初始化 3 将对象的reference赋值给instance
但因为Java Memory Model的问题,可能出现下面的所谓out-of-order write的问题:
1 分配内存 2 将对象的reference赋值给instance 3 构造函数初始化
也就是还没对对象初始化,就已经instance != null了,这样如果另外一个线程这时候对实例进行操作,可能有意想不到的结果。
四、单例模式的调用顺序示意图
由于单例模式有两种实现方式,那么它的调用顺序也分成两种。先看懒汉式的调用顺序,如图2所示:
图2 懒汉式调用顺序示意图
饿汉式的调用顺序,如图3所示:
图3 饿汉式调用顺序示意图
五、应用场景
在我们的应用中,有一个类AppConfig,用以读取配置文件中的配置信息,并在程序中需要配置信息时,被使用,由于配置信息是全局性的,因此在整个应用中,仅需要一个该类的实例,这样只需要读取一次配置文件,并且也会节省内存,避免不必要的内存浪费,此时就可以采用单例模式。