1.饿汉模式:
public class Singleton {
private Singleton() {
// 必须是私有构造方法
}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
因为饿汉模式在调用类的时候就new对象,这样浪费空间,所以考虑这种情况,有了懒汉模式
2.懒汉模式:
class Singleton {
private Singleton() {
//must
}
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在多线程的情况下,饿汉模式的判断对象是否为空语句会出现冲突,所以,再添加了是否线程安全的考虑
3.懒汉模式,考虑线程安全:
public class Singleton {
private Singleton() {
//must
}
private static Singleton instance = null;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在方法前面加了一个synchronized这样的关键字,给此方法加了同步锁。但是为了缩小锁的范围使得效率更高,就考虑到了双重检查锁。
4.双重检查锁:又叫做 双重检验锁模式(double checked locking pattern)
public class Singleton {
private Singleton() {
//must
}
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
上面的代码其实就是大名鼎鼎的DCK了。这个大大提高了并发访问性能,而且实现了延迟初始化。如果第一个 instance 不为 null 的话,就可以直接返回了,减少了同步开销。只有当 instance 是 null 的时候,才会进行同步操作。这个时候需要在进行一次 instance 是否为 null 的检查。因为,有可能两个线程都判断 instance 为 null,一个线程加锁,对instance进行了实例化,释放锁,另外一个线程拿到锁后,不进行 instance 是否为 null 的再次判断,会再次进行instance的实例化。
但是上面的代码还是有一块需要考虑,就是在new对象的时候其实java虚拟机是分成三步来操作的,
new 字节码对应的处理流程:
1给 instance 分配内存
2调用 Singleton 的构造函数来初始化成员变量(invokespecial)
3将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。所以,现在有一个关键字出现来解决这个问题。
5.volatile 实现
public class Singleton {
private Singleton() {
//must
}
private static volatile Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
JDK1.5 及之后版本增加了 volatile 关键字的一个语义:禁止指令重排序优化。
注:本人把单例模式写成五种,其实单例模式概念上讲只有两种,一个是饿汉模式 和 懒汉模式 ,下面的三种方式都是在懒汉模式运行性能上的优化。如果只学习java基础,看前前两个就足够了。但是后面三种也不容小觑哦