單例模式簡(jiǎn)介
單例模式是java創(chuàng)建型模式之一,主要作用是創(chuàng)建唯一對(duì)象舵匾。
單例模式特點(diǎn):
1.單例類只有一個(gè)實(shí)例飒炎。
2.單例類必須自己創(chuàng)建自己的唯一實(shí)例诡曙,即私有化構(gòu)造方法臀叙。
3.單例類必須給其他對(duì)象提供這一唯一實(shí)例。
單例常見實(shí)現(xiàn)
餓漢式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
餓漢式單例模式在單例類加載的時(shí)候就創(chuàng)建了單例對(duì)象岗仑,由于類加載有JVM控制執(zhí)行匹耕,其過程是線程安全的,所以餓漢式是線程安全的荠雕。
特點(diǎn):
1.線程安全
2.空間換取時(shí)間
懶漢式
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懶漢式單例模式在單例類加載的時(shí)候并沒有實(shí)例化單例對(duì)象,而是在獲取單例類唯一對(duì)象的方法中驶赏,先判斷單例對(duì)象是否為空炸卑,為空的時(shí)候?qū)嵗瘑卫龑?duì)象,然后將該對(duì)象返回給其他對(duì)象煤傍。由于第一次獲取時(shí)單例對(duì)象必定為空盖文,所以第一獲取時(shí)會(huì)有實(shí)例化對(duì)象的過程,執(zhí)行速度會(huì)比之后獲取時(shí)長(zhǎng)蚯姆。
特點(diǎn):
1.線程不安全(后面分析)
2.時(shí)間換取空間
線程安全下的單例模式
為什么懶漢式不是線程安全的五续?
如果有多個(gè)線程同時(shí)第一次調(diào)用getInstance方法,在第一個(gè)線程判斷instance為空進(jìn)入if語(yǔ)句塊準(zhǔn)備執(zhí)行 new Singleton()時(shí)龄恋,第二個(gè)線程也進(jìn)入了getInstance方法疙驾,由于第一個(gè)線程還未執(zhí)行完new Singleton()方法,此時(shí)instance對(duì)象為空郭毕,從而使第二個(gè)線程也進(jìn)入了if語(yǔ)句塊它碎。同樣的情況可能發(fā)生在N個(gè)線程中,從而instance可能被初始化N次。這樣就失去了單例的唯一性扳肛。
synchronized同步getInstance實(shí)現(xiàn)線程安全傻挂。
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
// public static Singleton getInstance() {
// synchronized (Singleton.class) {
// if (instance == null) {
// instance = new Singleton();
// }
// }
// return instance;
// }
}
將getInstance整個(gè)方法用sychronized關(guān)鍵字修飾,這樣在多線程訪問getInstance的時(shí)候同時(shí)進(jìn)入方法進(jìn)行判斷的只有一個(gè)線程挖息,這樣就可以避免多次實(shí)例化單例對(duì)象金拒。但是這樣鎖粒度太大了,導(dǎo)致多線程獲取實(shí)例化對(duì)象的效率大大降低套腹。(注釋部分同樣能保證線程安全殖蚕,但同樣所鎖密度太大)
DCL(Double Check Lock)
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
// public static Singleton getInstance(){
// if (instance == null)
//多個(gè)線程進(jìn)入此處,雖然后面的線程會(huì)等待鎖施放沉迹,
//但是同時(shí)也能進(jìn)入同步代碼塊睦疫,導(dǎo)致實(shí)例化多個(gè)單例對(duì)象,從而線程不安全鞭呕。
// synchronized (Singleton.class) {
// instance = new Singleton();
// }
// }
// return instance;
// }
}
分析注釋部分代碼蛤育,最后出現(xiàn)了DCL雙重鎖單例模式。多個(gè)線程雖然可以進(jìn)入第一個(gè)instance==null的if語(yǔ)句塊葫松,但是由于后續(xù)的同步代碼塊中又判斷了一次instance == null瓦糕,所以并不會(huì)實(shí)例化多個(gè)單例對(duì)象。從而保證了線程安全腋么。
DCL的隱患
DCL看似完美咕娄,但是依然存在隱患,而這個(gè)隱患就在instance = new Singleton()這個(gè)句代碼上珊擂。
在代碼看來只有一句圣勒,但是jvm在執(zhí)行這句語(yǔ)句的時(shí)候會(huì)有3個(gè)步驟
1.在java堆上分配一塊內(nèi)存M
2.在M上執(zhí)行實(shí)例化單例對(duì)象
3.將M地址指向instance
以上順序是我們需要的順序,然而JVM在執(zhí)行的時(shí)候會(huì)進(jìn)行指令重排和優(yōu)化摧扇,優(yōu)化后的執(zhí)行順序會(huì)成為:
1.在java堆上分配一塊內(nèi)存M
2.將M地址指向instance
3.在M上實(shí)例化單例對(duì)象
在多線程中圣贸,如果jvm按照優(yōu)化過后的順序執(zhí)行到2的時(shí)候,其他線程調(diào)用了getInstance()方法扛稽,此時(shí)在第一個(gè)判斷instance == null時(shí)會(huì)返回false吁峻,從而繞過同步代碼塊,直接返回instance對(duì)象引用在张,然而此時(shí)instance對(duì)象并沒有實(shí)例化完成用含,從而在其他線程調(diào)用的instance時(shí)發(fā)生NPE。
解決方法:此處的問題涉及到j(luò)ava并發(fā)編程中的有序性帮匾,使用volatile關(guān)鍵字修飾instance即可啄骇。
java并發(fā)編程之原子性,可見性辟狈,有序性(還沒寫^_^)
其他單例模式實(shí)現(xiàn)
1.靜態(tài)內(nèi)部類實(shí)現(xiàn)單例
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
特點(diǎn):延遲加載肠缔,線程安全
2.枚舉實(shí)現(xiàn)單例
public enum EnumSingleton {
/**
* 單例對(duì)象
*/
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
特點(diǎn):防止反射創(chuàng)建多個(gè)單例對(duì)象
3.使用容器實(shí)現(xiàn)單例
public class SingletonManager {
private static Map<String, Object> singletonMap = new HashMap<>();
private SingletonManager() {
}
public static void registerService(String key, Object singleton) {
if (singletonMap.containsKey(key)) {
throw new RuntimeException("重復(fù)注冊(cè)");
} else {
singletonMap.put(key, singleton);
}
}
public static Object getService(String key) {
if (singletonMap.containsKey(key)) {
return singletonMap.get(key);
} else {
return null;
}
}
}
特點(diǎn):方便管理夏跷,android中的服務(wù)使用此方式。
Kotlin單例object實(shí)現(xiàn)
在使用Kotlin語(yǔ)言時(shí)明未,實(shí)現(xiàn)單例是非常簡(jiǎn)單的
object Singleton{
}
通過kotlinc將Singleton的kt文件編譯成class文件:
public final class Singleton{
public static final SingletonINSTANCE;
private Singleton() {
}
static {
Singleton var0 = new Singleton();
INSTANCE = var0;
}
}
koltin中的object單例使用的是餓漢式實(shí)現(xiàn)的槽华,所以是線程安全的。