单例模式

 

1. 单例模式的定义

单例模式(Singleton Pattern)保证一个类仅有一个范例,并提供一个访问它的全局访问点。

单例模式是最简单、最常用的设计模式之一,属于创建型模式。

单例模式只涉及到一个类,该类负责创建自己的对象,而且确保只有单个对象被创建。另外,单例类提供了一种访问其唯一对象的方式,客户端可以直接使用,无需范例化该类的对象。

单例模式有三个特点

  • 1、单例类只能有一个范例。
  • 2、单例类必须自行创建自己的范例。
  • 3、单例类必须向系统的其它对象提供这一范例。

 

2. 单例模式的要点

1)目的

单例模式中的单例类负责创建自己的对象,而且确保只有单个对象被创建,并提供全局访问其对象的方法。

单例模式可以达到三个目的:

  • 1、控制资源的使用,通过线程同步控制资源的并发访问。
  • 2、控制范例产生的数量,达到节省资源的目的,同时提高运行效率。
  • 3、单例对象可以作为通信媒介,实现数据共享的,在多个线程或进程之间进行通信。

2)优点

  • 1、单例模式中的单例类只存在一个对象,可以节约系统资源。
  • 2、单例模式在需要频繁创建和销毁的对象的场景中,可以提高系统的性能。
  • 3、单例模式可以避免对共享资源的多重占用。

3)缺点

  • 1、单例模式中不适用于变化的对象,不能保存多个不同的状态。
  • 2、单例类的职责过重,在一定程度上违背了“单一职责原则”。
  • 3、单利模式中没有抽象层,因此单例类的扩展比较困难。

4)使用场景

  • 1、资源控制。
  • 每台计算机可连接多个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。

    常见的数据库连接池,整个系统通常只需要一个对象,无需范例化多份数据。


  • 2、数据同步
  • 网站的计数器,一般采用单例模式实现数据同步,否则计数可能会被不断覆盖。

 

3. 单例模式的范例

我们将创建一个单例类 SingleObject。SingleObject 类有它的私有构造函数和本身的一个静态范例。

SingleObject 类提供了一个静态方法,供外界获取它的静态范例。

客户端 SingletonPatternDemo 类使用 SingleObject 类来获取对象。

1)单例模式的结构

单例模式的 UML 图

2)单例模式的实现

package com.codebaoku.singleton;

/**
 * 单例类
 * @author 编程宝库
 *
 */
public class SingleObject {
 
   // 创建 SingleObject 的一个对象
   private static SingleObject instance = new SingleObject();
 
   // 构造函数为 private,禁止通过 new 范例化对象
   private SingleObject(){}
 
   // 获取唯一可用的对象的全局访问方法
   public static SingleObject getInstance(){
      return instance;
   }
 
   public void showMessage(){
      System.out.println("Hello World!");
   }
}

/**
 * 调用者客户端
 * @author 编程宝库
 *
 */
public class SingletonPatternDemo {
   public static void main(String[] args) {
 
      // 不合法的构造函数
      // 编译时错误:构造函数 SingleObject() 不可见
      // SingleObject object = new SingleObject();
 
      // 获取唯一可用的对象
      SingleObject object = SingleObject.getInstance();
 
      // 调用单例类对象,显示消息
      object.showMessage();
   }
}

执行程序,输出结果:

Hello World!

 

4. 单例模式的实现方式

单例模式有多种实现方式,在实现难易、线程安全、效率高低三个方面有所不同。

1)懒汉式,线程不安全

这是最基本的实现方式,唯一对象在获取方法中实现初始化,故称为懒汉式。

由于它没有考虑线程同步问题,所以不能在多线程中正常工作。

/**
 * 单例模式:懒汉式,线程不安全
 * @author 编程宝库
 *
 */
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
      if (instance == null) {  
          instance = new Singleton();  
      }  
      return instance;  
    }  
}

2)懒汉式,线程安全

这种方式的唯一对象也是在获取方法中实现初始化,属于懒汉式。

但是它考虑线程同步问题,所以能够在多线程中正常工作。

这种方式通过 synchronized 加锁来保证单例,但是加锁会影响效率。

/**
 * 单例模式:懒汉式,线程安全
 * @author 编程宝库
 *
 */
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
	    if (instance == null) {  
	        instance = new Singleton();  
	    }  
	    return instance;  
    }  
}

3)饿汉式

这种一种比较常用的方式,类加载时就初始化对象,但容易产生垃圾对象,浪费内存。

/**
 * 单例模式:饿汉式
 * @author 编程宝库
 *
 */
public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {
    	return instance;  
    }  
}

4)懒汉式,双重检测,线程安全

这种最常用的方式,采用双重检测机制,不仅能够保证线程安全,而且在多线程情况下保持高性能。

/**
 * 单例模式:懒汉式,双重检测
 * @author 编程宝库
 *
 */
public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
	        if (singleton == null) {  
	            singleton = new Singleton();  
	        }
        }  
    }  
    return singleton;  
    }  
}

5)选择合适的实现方式

一般情况下,不建议使用第 1 种和第 2 种懒汉方式,主要原因是这两种方式不是线程安全的。

最常用的是第 4 种带双重检测机制的懒汉方式 和 第 3 种饿汉方式。

将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。建造者模式属于创建型模式。