懒汉式(线程不安全)
(一)
public class Singleton {
private static Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) { // 1
instance = new Singleton(); // 2
}
return instance;
}
}
分析:
1、当线程A进入到第10行(#1)时,检查instance是否为空,此时是空的。
2、此时切换到线程B执行。线程B也进入到10行(#1)。同样检查instance为空,于是往下执行11行(#2),
创建了一个实例并返回。
3、再切换回线程A,由于之前检查到instance为空。所以也会执行11行(#2)创建实例并返回。
4、至此,两个实例被创建并返回,这不被希望。
解决线程安全:
使用synchronized关键字
(二)synchronized 升级
public class Singleton {
private static Singleton instance = null;
private Singleton() { }
public static synchronized Singleton getInstance() {
if (instance == null) { // 1
instance = new Singleton(); // 2
}
return instance;
}
}
分析:
在(一)中只有当instance还未被实例化时会产生"创建多个实例"问题;而当instance已经实例化完成后,
每次调用getInstance(),其实都是直接返回,即使是多个线程访问,也不会出问题。
在(二)中加上synchronized后,所有getInstance()的调用都必须要经过同步。其实只是在第一次调用时
要同步,所以此时会产生性能问题。
解决性能问题:
(三)性能 升级
public class Singleton {
private static Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) { // 1
synchronized (Singleton.class) {
instance = new Singleton(); // 2
}
}
return instance;
}
}
分析:
1、线程A和线程B同时进入第10行(#1)时,这时instance实例为空。
2、线程A进入synchronized块,创建实例,线程B等待。
3、线程A返回,线程B继续进入synchronized块,创建实例后返回。
4、这时又存在两个实例,问题与(一)相同。
继续解决性能问题(DCL双重检查加锁):
(四)性能 继续升级
public class Singleton {
private static Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) { // 1
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // 2
}
}
}
return instance;
}
}
继续分析:
当线程A返回,线程B进入synchronized块后,会先检查一下instance实例是否被创建,这时实例已经被线程A
创建,所以线程B不会再创建实例,而是直接返回。
————————————
注意:Java平台内存模型中有一个叫“无序写”(out-of-order writes)的机制。该机制导致该DCL方案失效。
Singleton instance = new Singleton(); 这行其实做了两个事情:
(1)、调用构造方法,创建了一个实例。
(2)、把这个实例赋值给instance这个实例引用。
在 “无序写”机制中,对于这两步JVM并不保证顺序。所以可能在调用构造方法前,instance已被设
置为非空。
[ Java 伪代码 ]
mem = allocate(); (1) // 为Singleton对象分配内存。
instance = mem; (2) // 此时instance非空,但是还没被初始化。
ctorSingleton(instance); (3) // 调用Singleton的构造函数,传递instance为实例引用赋值。
由此可见当一个线程执行到(2) 时instance已为非空,如果此时另一个线程进入程序判断instance为
非空,那么直接就跳转到return instance;而此时Singleton的构造方法还未调用instance,现在的值为
allocate()返回的内存对象。所以第二个线程得到的不是Singleton的一个对象实例,而是一个内存对象。
————————————
1、线程A进入getInstance()方法。
2、因为此时instance为空,所以线程A进入synchronized块。
3、线程A执行 instance = new Singleton(); 把实例变量instance在调用构造方法之前置为非空。
4、线程A退出,线程B进入。
5、线程B检查instance是否为空,此时不为空(在第3步已被线程A置为非空)。线程B返回instance的引用。
(此时instance的引用指向的并不是Singleton实例,因为没有调用构造方法。)
6、线程B退出,线程A进入。
7、线程A继续调用构造方法,完成instance的初始化,再返回。
继续解决DCL双重检查:
(五)DCL 升级
public class Singleton {
private static Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) { // 1
synchronized (Singleton.class) {
Singleton temp = instance; // 2
if (temp == null) {
synchronized (Singleton.class) { // 3
temp = new Singleton(); // 4
}
instance = temp; // 5
}
}
}
return instance;
}
}
继续分析:
1、线程A进入getInstance()方法。
2、因为instance是空,所以线程A进入位置#1的第一个synchronized块。
3、线程A执行位置#2的代码,把instance赋值给本地变量temp。instance为空,所以temp也为空。
4、因为temp为空,所以线程A进入位置#3的第二个synchronized块。
5、线程A执行位置#4的代码,把temp设置成非空,但还没有调用构造方法!("无序写"机制)
6、切换为线程B进入getInstance()方法。
7、因为instance为空,所以线程B试图进入第一个synchronized块。但由于锁已被线程A占用,
所以无法进入,线程B阻塞。
8、线程A激活,继续执行位置#4的代码。调用构造方法,生成实例。
9、将temp的实例引用赋值给instance。退出两个synchronized块。返回实例。
10、线程B激活,进入第一个synchronized块。
11、线程B执行位置#2的代码,把instance实例赋值给temp本地变量。
12、线程B判断本地变量temp不为空,所以跳过if判断。返回instance实例。
由此来看现在代码量比之前增加很多,很是麻烦!!!
继续优化: 饿汉式(预先初始化static成员变量)
(六)升级为 饿汉式
public class Singleton {
private static Singleton instance = new Singleton ();
private Singleton() { }
public static Singleton getInstance() {
return instance;
}
}
继续优化分析:
由于Java的机制,static的成员变量只在类加载的时候初始化一次,且类加载线程安全,但是牺牲懒加载,
创建完实例若不使用,耗费内存资源。
终极方案:
(七)内部类 终极方案
public class Singleton {
private static class SingletonHolder{
private static Singleton instance = new Singleton();
}
private SingletonFive() { }
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
终极分析:
由于Java机制,内部类SingletonHolder只有在getInstance()方法第一次调用的时候才会被加载(实现了Lazy),
而且其加载过程线程安全(实现线程安全)。内部类加载的时候实例化一次instance。