定义
什么是单例模式?
单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
举个例子 ——Windows任务管理器
通常情况下,无论我们启动任务管理多少次,Windows系统始终只能弹出一个任务管理器窗口。
为什么要这样设计呢?我们可以从以下两个方面来分析:其一,如果能弹出多个窗口,且这些窗口的内容完全一致,全部是重复对象,这势必会浪费系统资源,任务管理器需要获取系统运行时的诸多信息,这些信息的获取需要消耗一定的系统资源,包括CPU资源及内存资源等,而且根本没有必要显示多个内容完全相同的窗口;其二,如果弹出的多个窗口内容不一致,问题就更加严重了,这意味着在某一瞬间系统资源使用情况和进程、服务等信息存在多个状态。
两种类型
单例模式的类型
单例模式有两种类型:
懒汉式:在真正需要使用对象时才去创建该单例类
饿汉式:在类加载时已经创建好该单例对象,等待被程序使用
懒汉式创建对象的方法是在程序使用对象前,先判断该对象是否已经实例化,若已实例化直接返回该类对象,否则则先执行实例化操作。
饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可,即我们在编码时就已经指明了要马上创建这个对象,不需要等到被调用时再去创建。
关于类加载,涉及到JVM的内容,我们目前可以简单认为在程序启动时,这个单例对象就已经创建好了。
类图
- 懒汉式
class LazySingleton
{
private static LazySingleton instance = null;
private LazySingleton() { }
synchronized public static LazySingleton getInstance()
{
if (instance == null)
{
instance = new LazySingleton();
}
return instance;
}
}
public static LazySingleton getInstance()
{
if (instance == null)
{
synchronized (LazySingleton.class)
{
instance = new LazySingleton();
}
}
return instance;
}
该懒汉式单例类在getInstance()方法前面增加了关键字synchronized进行线程锁,以处理多个线程同时访问的问题。但是,上述代码虽然解决了线程安全问题,但是每次调用getInstance()时都需要进行线程锁定判断,在多线程高并发访问环境中,将会导致系统性能大大降低。
由于实现了synchronized加锁机制,线程A进入synchronized锁定的代码中执行实例创建代码,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized锁定代码。但当A执行完毕时,线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象,违背单例模式的设计思想,因此需要进行进一步改进,在synchronized中再进行一次(instance == null)判断,这种方式称为双重检查锁定(Double-Check Locking)。使用双重检查锁定实现的懒汉式单例类完整代码如下所示:
class LazySingleton
{
private volatile static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance()
{
//第一重判断
if (instance == null)
{
//锁定代码块
synchronized (LazySingleton.class)
{
//第二重判断
if (instance == null)
{
instance = new LazySingleton(); //创建单例实例
}
}
}
return instance;
}
}
- 饿汉式
从上图中可以看出,由于在定义静态变量的时候实例化单例类,因此在类加载的时候就已经创建了单例对象,代码如下所示:
class EagerSingleton
{
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
public static EagerSingleton getInstance()
{
return instance;
}
}
实现举例
再以上面任务管理器为例:
class TaskManager
{
private static TaskManager tm = null;
public TaskManager() {……} //初始化窗口
public void displayProcesses() {……} //显示进程
public void displayServices() {……} //显示服务
public static TaskManager getInstance()
{
if (tm == null)
{
tm = new TaskManager();
}
return tm;
}
}
class TaskManager
{
private static TaskManager tm = new TaskManager();
public TaskManager() {……} //初始化窗口
public void displayProcesses() {……} //显示进程
public void displayServices() {……} //显示服务
public static TaskManager getInstance()
{
return tm;
}
}
小结
饿汉式单例类在类被加载时就将自己实例化,它的优点在于无须考虑多线程访问问题,可以确保实例的唯一性;从调用速度和反应时间角度来讲,由于单例对象一开始就得以创建,因此要优于懒汉式单例。
但是无论系统在运行时是否需要使用该单例对象,由于在类加载时该对象就需要创建,因此从资源利用效率角度来讲,饿汉式单例不及懒汉式单例,而且在系统加载时由于需要创建饿汉式单例对象,加载时间可能会比较长。