定義: 某一種對(duì)象,全局只能創(chuàng)建一個(gè)。
單例模式猶如茴香豆有N中寫法。
- 構(gòu)造函數(shù)設(shè)置成私有
- 靜態(tài)構(gòu)造方法構(gòu)造對(duì)象。
public class Singleton {
private static final Singleton s = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return s;
}
}
按照java的語法,都知道靜態(tài)變量會(huì)自動(dòng)初始化,并且final的變量只會(huì)初始化一次并且引用不可改變,這樣我們只有一個(gè)Singleton對(duì)象的引用。
但是這個(gè)Singleton對(duì)象什么時(shí)候創(chuàng)建出來的呢,什么時(shí)候執(zhí)行的呢?
反編譯一下:
Classfile /root/java/Singleton.class
Last modified Jun 4, 2018; size 369 bytes
MD5 checksum 1820528d8fe309e7c6bff10a17dd9113
Compiled from "Singleton.java"
public class Singleton
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#17 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#18 // Singleton.s:LSingleton;
#3 = Class #19 // Singleton
#4 = Methodref #3.#17 // Singleton."<init>":()V
#5 = Class #20 // java/lang/Object
#6 = Utf8 s
#7 = Utf8 LSingleton;
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 getInstance
#13 = Utf8 ()LSingleton;
#14 = Utf8 <clinit>
#15 = Utf8 SourceFile
#16 = Utf8 Singleton.java
#17 = NameAndType #8:#9 // "<init>":()V
#18 = NameAndType #6:#7 // s:LSingleton;
#19 = Utf8 Singleton
#20 = Utf8 java/lang/Object
{
public static Singleton getInstance();
descriptor: ()LSingleton;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #2 // Field s:LSingleton;
3: areturn
LineNumberTable:
line 6: 0
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: new #3 // class Singleton
3: dup
4: invokespecial #4 // Method "<init>":()V
7: putstatic #2 // Field s:LSingleton;
10: return
LineNumberTable:
line 2: 0
}
SourceFile: "Singleton.java"
可以看到,s的初始化放到了static()方法里,也就是類構(gòu)造器(cinit);
public class Singleton {
private static final Singleton s
static {
s = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return s;
}
}
這個(gè)方法什么時(shí)候執(zhí)行呢?
類初始化的時(shí)候
類的生命周期是:
加載,驗(yàn)證,準(zhǔn)備,解析,初始化,使用,卸載。
當(dāng)使用的時(shí)候,一般都是 Singleton a = Singleton.getInstance(); 此時(shí)遇到了指令invokestatic ,類必須要初始化,Singleton類 進(jìn)行加載,驗(yàn)證,準(zhǔn)備(清零),解析(接口,字段解析),初始化。
jvm在執(zhí)行cinit方法時(shí),會(huì)采取加鎖和同步的方式。避免其他線程也執(zhí)行,并且在本線程執(zhí)行完畢后,其他線程也無法執(zhí)行。
因此該對(duì)象的創(chuàng)建是線程安全的。
大多數(shù)情況下,這樣設(shè)計(jì)就可以的。
進(jìn)一步要考慮的是懶加載(調(diào)用的時(shí)候再創(chuàng)建,不要再加載的時(shí)候創(chuàng)建),線程安全,以及序列化安全。
如果創(chuàng)建該對(duì)象的時(shí)間很長,可以采用一些辦法,在使用的時(shí)候再創(chuàng)建。
內(nèi)部類懶加載
public class Single {
private Single() {
}
private static class Holder {
private final static Single s = new Single();
}
public static Single getInstance() {
return Holder.s;
}
}
這樣Single類如果因?yàn)槠渌闆r,發(fā)生了加載,也不會(huì)初始化s。
只有調(diào)用getInstance() 時(shí),加載Single類,執(zhí)行Holder.s ,對(duì)應(yīng)指令是invokestatic,加載Holder類,初始化s,并返回。這個(gè)過程是jvm控制加鎖,線程安全。
序列化安全使用枚舉即可,避免反射和序列化的攻擊。
不過jdk打算把序列化從jdk中移除出去,JDK認(rèn)為有至少三分之一的安全問題都是Serializable引起的。