单例模式是最简单的设计模式之一,用于保证系统中只有一个类的实例的场景。
单例模式的特点:
- 只有一个实例
- 只能自己创建自己的实例
- 能够对外提供自己创建的实例
1. 程序示例
1.1 懒汉式
在 Gof 的设计模式中介绍单例模式使用的说明代码叫做懒汉式,java 版代码如下:
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这是最简单的代码实现,但是这个代码存在问题,即在多线程的程序中,不能保证一定是单例的。原因也很简单,代码被编译成字节码被执行,有可能在刚进入 if 判断后时间分片就用完了,另一个线程也进入 if 判断,从而创建多个实例。一种简单的处理方法是将方法同步。
synchronized public static Singleton getInstance() {
}
1.2 饿汉式
另外还有一种比较常见的叫做饿汉式,这种方式更符合 java 的风格。
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
1.3 静态内部类
通常情况下,直接使用恶汉式是最安逸的,况且内存越来越不是瓶颈了。但是如果还对装载类时初始化耿耿于怀,可以使用这种方式。
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
使用静态内部类的好处是不会在装载 Singleton 的时候就实例化,而是在调用 getInstance() 方法时才创建实例。
1.4 枚举
在 jdk1.5 以后加入了枚举,也可以使用枚举来实现单例。
public enum Singleton {
INSTANCE;
public void whateverMethod(){
}
}
使用 enum 方式创建单例的好处是简单,简单到让人看不懂。怎么使用可参考一个例子 枚举单例读取配置文件 。
1.5 双重校验锁
网上很多单例的文章中都提到了双重校验锁的方式。这种方式出现的目的是对懒汉式进行优化。
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
在《java 与模式》一书中针对这种方式也做了分析,这种方式并不能真正达到效果,所以还是安心使用饿汉吧。
2016-08-08 补充:
网上一些资料中提到,自 JDK 1.5 以后,修复了 volatile 关键字的 bug ,所以,JDK 1.5 以后双重校验锁可以正常工作了。关于 volatile 关键字更多知识点,参考 Java 关键字 volatile
2. 类图
如果要画出常用设计模式的类图,单例无疑是最简单的了。