单例模式
1. 单例模式的定义
单例模式(Singleton Pattern)保证一个类仅有一个范例,并提供一个访问它的全局访问点。
单例模式是最简单、最常用的设计模式之一,属于创建型模式。
单例模式只涉及到一个类,该类负责创建自己的对象,而且确保只有单个对象被创建。另外,单例类提供了一种访问其唯一对象的方式,客户端可以直接使用,无需范例化该类的对象。
单例模式有三个特点
- 1、单例类只能有一个范例。
- 2、单例类必须自行创建自己的范例。
- 3、单例类必须向系统的其它对象提供这一范例。
2. 单例模式的要点
1)目的
单例模式中的单例类负责创建自己的对象,而且确保只有单个对象被创建,并提供全局访问其对象的方法。
单例模式可以达到三个目的:
- 1、控制资源的使用,通过线程同步控制资源的并发访问。
- 2、控制范例产生的数量,达到节省资源的目的,同时提高运行效率。
- 3、单例对象可以作为通信媒介,实现数据共享的,在多个线程或进程之间进行通信。
2)优点
- 1、单例模式中的单例类只存在一个对象,可以节约系统资源。
- 2、单例模式在需要频繁创建和销毁的对象的场景中,可以提高系统的性能。
- 3、单例模式可以避免对共享资源的多重占用。
3)缺点
- 1、单例模式中不适用于变化的对象,不能保存多个不同的状态。
- 2、单例类的职责过重,在一定程度上违背了“单一职责原则”。
- 3、单利模式中没有抽象层,因此单例类的扩展比较困难。
4)使用场景
- 1、资源控制。
- 2、数据同步
每台计算机可连接多个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。
常见的数据库连接池,整个系统通常只需要一个对象,无需范例化多份数据。
网站的计数器,一般采用单例模式实现数据同步,否则计数可能会被不断覆盖。
3. 单例模式的范例
我们将创建一个单例类 SingleObject。SingleObject 类有它的私有构造函数和本身的一个静态范例。
SingleObject 类提供了一个静态方法,供外界获取它的静态范例。
客户端 SingletonPatternDemo 类使用 SingleObject 类来获取对象。
1)单例模式的结构
2)单例模式的实现
package com.aizws.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 种饿汉方式。
下一章:建造者模式
将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。建造者模式属于创建型模式。