单例模式的定义与特点
单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。
单例模式有 3 个特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点;
单例模式的结构与实现
单例模式是设计模式中最简单的模式之一。通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。
下面看单例的实现:
1. 饿汉式(静态常量)
public class Singleton01 {
private static Singleton01 instance;
static {
instance = new Singleton01();
}
private Singleton01() { }
public static Singleton01 getInstance() {
return instance;
}
}
/**
* 单例,饿汉式,静态变量
*
* @author Administrator
*/
public class GetSingletonTest {
public static void main(String[] args) {
Singleton01 singleton1 = Singleton01.getInstance();
Singleton01 singleton2 = Singleton01.getInstance();
System.out.println(singleton1 == singleton2);
}
}
优缺点说明:
1. 优点:这种写法比较简单,就是在装载的时候就完成实例化。避免了线程同步问题。
2. 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从开始在结束始终都没有使用过这个实例,则会造成内存的浪费。
3. 这种方式基于ClassLoader机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getInstance() 方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式导致类装载,这时候初始化instance就没有达到lazy loading 效果
4. 这种单例模式可用,但可能造成内存浪费。
2. 懒汉式(线程安全,同步方法)
public class Singleton02 {
private static Singleton02 instance;
private Singleton02() { }
/**
* 添加synchronized,线程安全,防止并发问题
*/
public static synchronized Singleton02 getInstance() {
if (instance == null) {
instance = new Singleton02();
}
return instance;
}
}
优缺点说明:
1. 解决了线程不安全问题
2. 效率太低了,每个线程在想获取类的实例的时候,执行 getInstance() 方法都要进行同步。而其实这个方法只执行了一次实例代码就够了,后面的想获取该类实例,直接return就行,方法进行同步效率太低。
3. 结论:在实际开发中,不推荐使用
3. 懒汉式(DoubleCheck)
/**
* Double check 写法
*/
public class Singleton03 {
private static volatile Singleton03 instance;
private Singleton03() {
}
public static Singleton03 getInstance() {
if (instance == null) {
synchronized (Singleton03.class) {
if (instance == null) {
instance = new Singleton03();
}
}
}
return instance;
}
}
优缺点说明:
1. Double-Check概念是多线程开发中常使用到的,如代码所示,进行了两次 if (instance == null) 判断,线程安全。而使用了 volatile 关键字,使得 instance 在各个线程中可见。
2. 线程安全,延迟加载,效率较高
3. 结论:在实际开发中,推荐使用这种单例模式
4. 静态内部类
/**
* 使用静态内部类实现
*/
public class Singleton04 {
private Singleton04() {
}
private static class Singleton04Instance {
private static final Singleton04 INSTANCE = new Singleton04();
};
public static synchronized Singleton04 getInstance() {
return Singleton04Instance.INSTANCE;
}
}
优缺点说明:
1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
2. 静态内部类方式在 Singleton04 装载时不会立即实例化,而是在调用 getInstance() 方法时,才会加载 Singleton04Instance 类, 从而完成 Singleton04 的实例化。
3. 类的静态属性只会在第一次加载类的时候进行初始化,所以这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程无法进入。
4. 结论:推荐使用
5. 枚举方式
public enum Singleton05 {
INSTANCE;
public void sayOk() {
System.out.println("Singleton05 ...");
}
}
优缺点说明:
1. 这借助 JDK1.5 中添加的枚举来实现单例实例,不仅能避免多线程同步问题,而且还能防止反序列化重新创建对象。
2. 这种方法是 Effective Java 作者 Josh Bloch 所推荐的。
3. 结论:推荐使用
单例模式注意事项和说明
1. 单例模式保证了系统内存中该类只存在该类的一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
2. 当想实例化一个单例的时候,必须要记住使用相应的获取对象的方法,而不是使 new
3. 单例模式使用的场景:需要频繁的进行创建和销毁的对象,创建对象时消耗时过多或耗费资源过多(重量级对象),但有经常使用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂)。