(1)餓漢模式
public class Singleton{
private Singleton(){};
private static final Singleton INSTANCE = new Singleton();
public static Singleton getInstance(){
renturn INSTANCE;
}
}
分析: 第一次加載到內(nèi)存中的就會(huì)被初始化 ,并對(duì)外提供一個(gè)獲取該實(shí)例對(duì)象的方法,優(yōu)點(diǎn):立即執(zhí)行初始化,線程安全。缺點(diǎn):會(huì)受到反射,序列化影響,浪費(fèi)資源
(2)懶漢模式:線程不安全,單線程下只在第一次調(diào)用時(shí)才會(huì)實(shí)例化
public class Singleton{
private Singleton(){};
private static Singleton singleton = null;
public static Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
懶漢加鎖模式(1):線程安全,但是每次調(diào)用都會(huì)加鎖,效率很低
public class Singleton{
private Singleton(){};
private static Singleton singleton = null;
public static synchronized Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
雙重檢查鎖的懶漢單例模式(2):保證線程安全,只有在singleton為null時(shí)才會(huì)加鎖,優(yōu)化鎖帶來(lái)的消耗
public class Singleton{
private static volatile Singleton singleton = null;
private Singleton(){};
public static Singleton getInstance(){
if(singleton == null){
synchronized(Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
(3)靜態(tài)內(nèi)部類(lèi)延遲初始化,線程安全
public class Singleton {
private Singleton(){};
private static class initInstance{
private static final Singleton SINGLETON = new Singleton();
}
public static Singleton getSingleton(){
return initInstance.SINGLETON;
}
}
分析:根據(jù)JVM規(guī)范,當(dāng)?shù)谝淮握{(diào)用Singleleton.getInstance( )時(shí),Singleleton被首次調(diào)用,JVM對(duì)其進(jìn)行初始化操作,此時(shí)不會(huì)調(diào)用Singleleton的構(gòu)造方法。接下來(lái)會(huì)調(diào)用getInstance()方法,又首次主動(dòng)使用initInstance()內(nèi)部類(lèi),所以JVM會(huì)對(duì)initInstance()進(jìn)行初始化操作,在這個(gè)初始化操作中,靜態(tài)常量INSTANCE被賦值時(shí)才調(diào)用Singleton()構(gòu)造方法,完成實(shí)例化并返回該對(duì)象。
當(dāng)再次調(diào)用Singleleton.getInstance( )時(shí),因?yàn)橐呀?jīng)進(jìn)行過(guò)初始化操作了,就不會(huì)再進(jìn)行初始化操作,直接返回INSTANCE常量。
解決反射與序列化的影響思路:(以餓漢單例模式為例)
public class Singleton{
private Singleton(){
//在私有的無(wú)參構(gòu)造器中進(jìn)行判斷,這樣可以解決反射問(wèn)題
if (INSTANCE != null){
throw new RuntimeException("禁止反射創(chuàng)建單例模式");
}
};
private static final Singleton INSTANCE = new Singleton();
public static Singleton getInstance(){
return INSTANCE;
}
//添加readResolve()方法可以防止序列化問(wèn)題
private Object readResolve() throws ObjectStreamException{
return INSTANCE;
}
}
枚舉類(lèi)單例模式:可以滿(mǎn)足線程安全,天生防止序列化生成新的實(shí)例,防止反射攻擊
public class Singleton{}
public enum SingletonEnum{
INSTANCE;
private Singleton singleton = null;
private SingletonEnum(){
singleton = new Singleton();
}
public Singleton getInstance(){
return singleton;
}
}
調(diào)用:
Singleton singleton = SingletonEnum.INSTANCE.getInstance();
Enum是一個(gè)普通的類(lèi),繼承自Java.lang.Enum類(lèi)
public enum SingletonEnum{
INSTANCE;
}
將上面枚舉編譯后的字節(jié)碼反編譯,得到如下代碼:
public abstract class SingletonEnum extends Enum<SingletonEnum>{
public static final SingletonEnum INSTANCE;
public static SIngletonEnum[]values();
public static SingletonEnum valuesof(String s);
static{};
}
通過(guò)反編譯后得到的代碼可知:
INSTANCE 被聲明為static,虛擬機(jī)會(huì)保證一個(gè)類(lèi)的<clinit>() 方法在多線程環(huán)境中被正確的加鎖、同步。所以,枚舉實(shí)現(xiàn)是在實(shí)例化時(shí)是線程安全。
接下來(lái)再看序列化的問(wèn)題
在Java的規(guī)范中,每一個(gè)枚舉類(lèi)型及其定義的枚舉變量在JVM中都是唯一的,在枚舉類(lèi)型的序列化與反序列化中,JVM做了一些特殊的規(guī)定:在序列化時(shí),Java僅僅只是將枚舉對(duì)象的name屬性輸出到結(jié)果中,反序列化時(shí)則是通過(guò)Java.lang.Enum的valuesof()方法根據(jù)名稱(chēng)獲取枚舉對(duì)象。
比如:
public enum SingletonEnum{
INSTANCE;
}
上面枚舉類(lèi)在序列化時(shí),只將 INSTANCE 這個(gè)名稱(chēng)輸出,反序列化時(shí),再通過(guò)這個(gè)名稱(chēng),尋找對(duì)應(yīng)的枚舉類(lèi)型,因此反序列化后的實(shí)例與之前被序列化的對(duì)象實(shí)例相同
接下來(lái)看反射的問(wèn)題
枚舉單例
public enum Singleton {
INSTANCE {
@Override
protected void read() {
System.out.println("read");
}
@Override
protected void write() {
System.out.println("write");
}
};
protected abstract void read();
protected abstract void write();
}
反編譯后
public abstract class Singleton extends Enum
{
private Singleton(String s, int i)
{
super(s, i);
}
protected abstract void read();
protected abstract void write();
public static Singleton[] values()
{
Singleton asingleton[];
int i;
Singleton asingleton1[];
System.arraycopy(asingleton = ENUM$VALUES, 0, asingleton1 = new Singleton[i = asingleton.length], 0, i);
return asingleton1;
}
public static Singleton valueOf(String s)
{
return (Singleton)Enum.valueOf(singleton/Singleton, s);
}
Singleton(String s, int i, Singleton singleton)
{
this(s, i);
}
public static final Singleton INSTANCE;
private static final Singleton ENUM$VALUES[];
static
{
INSTANCE = new Singleton("INSTANCE", 0) {
protected void read()
{
System.out.println("read");
}
protected void write()
{
System.out.println("write");
}
};
ENUM$VALUES = (new Singleton[] {
INSTANCE
});
}
}
①類(lèi)的修飾為abstract,沒(méi)法實(shí)例化
②在static中完成實(shí)例化的,所以線程安全
從源碼上來(lái)看
java.lang.reflect 中的 Constructor 的newInstance中規(guī)定了不準(zhǔn)通過(guò)反射創(chuàng)建枚舉對(duì)象
(反射在通過(guò)newInstance創(chuàng)建對(duì)象時(shí),會(huì)檢查該類(lèi)是否是枚舉類(lèi),如果是,則拋出異常,反射失敗。)
