單例模式:保證一個(gè)類只有一個(gè)實(shí)例,并且提供一個(gè)可以訪問的全局入口。
優(yōu)點(diǎn):節(jié)省內(nèi)存、節(jié)省計(jì)算、方便管理
應(yīng)用舉例:無狀態(tài)的工具類(日志工具、字符串工具)、全局信息類(全局記數(shù)、環(huán)境變量)
常見的五種寫法:從簡(jiǎn)單到難遞進(jìn)
餓漢式寫法:
優(yōu)點(diǎn):類裝載的時(shí)候就完成了初始化,避免了線程同步的問題。
缺點(diǎn):從始至終沒有使用實(shí)例,造成內(nèi)存浪費(fèi)。
/**
* 餓漢式:
*/
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return singleton;
}
}
靜態(tài)代碼塊:
優(yōu)點(diǎn):類裝載的時(shí)候就完成了初始化,避免了線程同步的問題。同(餓漢式)
缺點(diǎn):從始至終沒有使用實(shí)例,造成內(nèi)存浪費(fèi)。同(餓漢式)
/**
* 靜態(tài)代碼塊
*/
public class Singleton {
private static Singleton singleton;
static {
singleton = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return singleton;
}
}
懶漢式:
優(yōu)點(diǎn):在getInstance被調(diào)用的時(shí)候,才實(shí)例化對(duì)象。
缺點(diǎn):只能在單線程下使用,多想成環(huán)境下,如果一個(gè)線程通過 if (singleton == null) {}還沒往下執(zhí)行,另一個(gè)線程也通過if (singleton == null) {}這時(shí)會(huì)多次創(chuàng)建實(shí)例。
/**
* 懶漢式
*/
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
線程安全懶漢式:
優(yōu)點(diǎn):解決了懶漢式線程安全問題。
缺點(diǎn):效率太低,多個(gè)線程不能同時(shí)訪問getInstance()方法,不能防止反序列化,生成多個(gè)實(shí)例。
/**
* 線程安全懶漢式
*/
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
雙重檢查模式:
優(yōu)點(diǎn):實(shí)例代碼只調(diào)用一次,后來判斷**if (singleton == null) 只需要判斷外層即可跳出,同時(shí)線程安全,延遲加載,效率更高。
缺點(diǎn):效率太低,多個(gè)線程不能同時(shí)訪問getInstance()方法。
1、為什么要判斷兩次為空?去掉第二層可以嗎?
答案:兩個(gè)線程同時(shí)調(diào)用getInstance方法,并且singleton為null,兩個(gè)線程都可以通過第一層為null判斷,由于鎖機(jī)制的存在,一個(gè)線程執(zhí)行完singleton = new Singleton()之后退出synchronized保護(hù)區(qū)域,如果沒有第二層if (singleton == null)判斷,第二個(gè)線程也會(huì)執(zhí)行singleton = new Singleton(),這樣就破壞了單例。
如果去掉第一個(gè) if (singleton == null)所有線程會(huì)串行執(zhí)行,效率低下
2、為什么要加volatile?
答案:singleton = new Singleton()并非原子操作,在JVM中至少做了三件事。
1)給singleton分配內(nèi)存空間
2)調(diào)用Singleton的構(gòu)造函數(shù)來初始化singleton
3)將singleton對(duì)象指向分配的內(nèi)存空間(執(zhí)行完singleton就不是null了)
存在重排序的優(yōu)化,可能執(zhí)行順序?yàn)?)3)2),當(dāng)執(zhí)行完3)后對(duì)象已經(jīng)實(shí)例化,但并未初始化,其他線程使用該對(duì)象就會(huì)報(bào)錯(cuò),其他線程使用報(bào)錯(cuò)后,對(duì)象才被初始化,可是已經(jīng)晚了。
** volatile可以禁止指令重排,使其按照1)2)3)順序執(zhí)行
/**
* 雙重檢查模式
*/
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
靜態(tài)內(nèi)部類的寫法:
優(yōu)點(diǎn):線程安全,需要實(shí)例時(shí)才時(shí)例化,延遲加載,效率高。
缺點(diǎn):不能防止反序列化,生成多個(gè)實(shí)例。
/**
* 靜態(tài)內(nèi)部類
*/
public class Singleton {
private Singleton() {
}
private static class SingletonInstance {
private static final Singleton singleton = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.singleton;
}
}
枚舉式寫法
優(yōu)點(diǎn):避免多線程同步,JVM保證線程安全(反編譯之后看到各個(gè)枚舉項(xiàng)目通過static來定義和初始化,類加載時(shí)被初始化),防止反序列化、反射,破壞單例。其中java還針對(duì)枚舉類序列化做了規(guī)定,僅僅講枚舉類的name屬性輸出到結(jié)果中,反序列化時(shí)通過java.lang.Enum的valueof根據(jù)名字查找對(duì)西那個(gè),而不是新建一個(gè)對(duì)象。如果反射枚舉類會(huì)拋出異常。
/**
* 枚舉式
*/
public enum Singleton {
INSTANCE;
public void whateverMethod() {
System.out.println("執(zhí)行了單例方法,例如返回環(huán)境變量信息");
}
public static void main(String[] args) {
//使用枚舉寫法表示的單例類
Singleton.INSTANCE.whateverMethod();
}
}