public class Singleton {
private Singleton() {
// 必须是私有构造方法
}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
上面的写法最简单最有效并且是线程安全的(由JVM保证)。但是没有实现延迟初始化。
注意:
不延迟加载有什么问题?
class Singleton {
private Singleton() {
//must
}
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这段代码使用了懒加载模式,但是却存在致命的问题。当有多个线程同时调用 getInstance() 的时候,就会创建多个实例(同时判定instance == null)。也就是说在多线程下不能正常工作。
public class Singleton {
private Singleton() {
//must
}
private static Singleton instance = null;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
注意:
上面的代码实现了延迟加载。但是存在一些问题。如果 getInstance 的调用频率很高的话,每次都 synchronized 同步访问,效率岂不是很低。因此,要降低锁的力度。
双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。程序员称其为双重检查锁,因为会有两次检查 instance == null
,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。
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的实例化。
上面代码有一个问题:instance 不是 volatile 的。看 new 操作的反汇编代码,其实他包含3条汇编指令:new、dup、init。这样当一个线程执行了new汇编指令后,被唤出。一个新的线程到来,在第一个判断 instance 是否为null时,发现 instance 已经不为null了,直接返回。这时的instance其实是不完整的。而且,即使一个线程实例化了 instance,由于每个线程都有自己的working 缓存,可能另一个线程看不到前一个线程对 instance 的操作。
new 字节码对应的处理流程:
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
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 关键字的一个语义:禁止指令重排序优化。
volatile 的两层语义:
public class Singleton {
private Singleton() {
//must, otherwise JVM create default constructor(public)
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
}
内部的静态类 SingletonHolder,只有当 getInstance 方法被调用时才会被加载。注意 SingletonHolder 这个内部类是 static 的,否则其成员变量不能声明成 static 的。JVM在类加载时保证了线程安全性问题,以及 instance 的唯一性。
说到底就是 JVM 本身保证了只能有一个线程能够完成对某个类的加载,也即 JVM 中仅有一个该类的实例(Class的实例)。
上面提到的几种实现单例的方式都有共同的缺点:
1)需要额外的工作来实现序列化,否则每次反序列化一个序列化的对象时都会创建一个新的实例。
2)可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。
而枚举类很好的解决了这两个问题,使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,《Effective Java》作者推荐使用的方法。不过,在实际工作中,很少看见有人这么写。
public enum Singleton {
INSTANCE
}
我们可以通过 Singleton.INSTANCE 来访问实例,这比调用 getInstance() 方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。
javap -v Singleton
Classfile /Users/n3verl4nd/Desktop/Singleton.class
Last modified 2018-8-8; size 730 bytes
MD5 checksum 4820457d608bac02cd356d7af672026d
Compiled from "Singleton.java"
public final class Singleton extends java.lang.Enum<Singleton>
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
Constant pool:
#1 = Fieldref #4.#29 // Singleton.$VALUES:[LSingleton;
#2 = Methodref #30.#31 // "[LSingleton;".clone:()Ljava/lang/Object;
#3 = Class #14 // "[LSingleton;"
#4 = Class #32 // Singleton
#5 = Methodref #10.#33 // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#6 = Methodref #10.#34 // java/lang/Enum."":(Ljava/lang/String;I)V
#7 = String #11 // INSTANCE
#8 = Methodref #4.#34 // Singleton."":(Ljava/lang/String;I)V
#9 = Fieldref #4.#35 // Singleton.INSTANCE:LSingleton;
#10 = Class #36 // java/lang/Enum
#11 = Utf8 INSTANCE
#12 = Utf8 LSingleton;
#13 = Utf8 $VALUES
#14 = Utf8 [LSingleton;
#15 = Utf8 values
#16 = Utf8 ()[LSingleton;
#17 = Utf8 Code
#18 = Utf8 LineNumberTable
#19 = Utf8 valueOf
#20 = Utf8 (Ljava/lang/String;)LSingleton;
#21 = Utf8 <init>
#22 = Utf8 (Ljava/lang/String;I)V
#23 = Utf8 Signature
#24 = Utf8 ()V
#25 = Utf8 <clinit>
#26 = Utf8 Ljava/lang/Enum<LSingleton;>;
#27 = Utf8 SourceFile
#28 = Utf8 Singleton.java
#29 = NameAndType #13:#14 // $VALUES:[LSingleton;
#30 = Class #14 // "[LSingleton;"
#31 = NameAndType #37:#38 // clone:()Ljava/lang/Object;
#32 = Utf8 Singleton
#33 = NameAndType #19:#39 // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#34 = NameAndType #21:#22 // "":(Ljava/lang/String;I)V
#35 = NameAndType #11:#12 // INSTANCE:LSingleton;
#36 = Utf8 java/lang/Enum
#37 = Utf8 clone
#38 = Utf8 ()Ljava/lang/Object;
#39 = Utf8 (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
{
public static final Singleton INSTANCE;
descriptor: LSingleton;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static Singleton[] values();
descriptor: ()[LSingleton;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #1 // Field $VALUES:[LSingleton;
3: invokevirtual #2 // Method "[LSingleton;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[LSingleton;"
9: areturn
LineNumberTable:
line 1: 0
public static Singleton valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)LSingleton;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #4 // class Singleton
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class Singleton
9: areturn
LineNumberTable:
line 1: 0
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
0: new #4 // class Singleton
3: dup
4: ldc #7 // String INSTANCE
6: iconst_0
7: invokespecial #8 // Method "":(Ljava/lang/String;I)V
10: putstatic #9 // Field INSTANCE:LSingleton;
13: iconst_1
14: anewarray #4 // class Singleton
17: dup
18: iconst_0
19: getstatic #9 // Field INSTANCE:LSingleton;
22: aastore
23: putstatic #1 // Field $VALUES:[LSingleton;
26: return
LineNumberTable:
line 2: 0
line 1: 13
}
Signature: #26 // Ljava/lang/Enum;
SourceFile: "Singleton.java"
http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/
https://blog.csdn.net/fan2012huan/article/details/53454724
https://blog.csdn.net/goodlixueyong/article/details/51935526