簡單的單例模式:(懶漢式)
package com.zcp.juc.single;
/**
* @author zcp
* @description
* @created by 2020-03-26 22:50
*/
public final class Singleton {
private static Singleton INSTANCE=null;
private Singleton(){
}
public static Singleton getInstance(){
if(INSTANCE==null){
INSTANCE=new Singleton();
}
return INSTANCE;
}
}
通俗講:單例=單個(gè)實(shí)例,即每次獲取相同的對象實(shí)例撵儿,因?yàn)閖ava中創(chuàng)建對象需要消耗資源,單例模式正好解決了對象的頻繁創(chuàng)建狐血。那么懶漢式是什么呢统倒?懶唄,我就是不想那么早初始化氛雪,有需要我再初始化房匆。
很多人都以為懶漢式寫到這,可是在多線程環(huán)境下呢报亩?上述代碼完了嗎浴鸿?
沒有!O易贰岳链!
問題來了:
有兩個(gè)線程T1、T2劲件,當(dāng)線程T1執(zhí)行到if條件判斷時(shí)掸哑,發(fā)現(xiàn)INSTANCE==null,還沒創(chuàng)建實(shí)例呢,T2線程也走到了這個(gè)if條件判斷零远,發(fā)現(xiàn)INSTANCE==null苗分,那么兩條線程繼續(xù)向下執(zhí)行,就會(huì)導(dǎo)致new了兩個(gè)對象牵辣,這顯然不符合單例模式摔癣,不是我們想要的結(jié)果。
怎么辦的纬向?
在有可能發(fā)生問題的地方加鎖择浊,不知道在哪?沒關(guān)系逾条,把整個(gè)方法都加上鎖
package com.zcp.juc.single;
/**
* @author zcp
* @description
* @created by 2020-03-26 22:50
*/
public final class Singleton {
private static Singleton INSTANCE=null;
private Singleton(){
}
public static synchronized Singleton getInstance(){
if(INSTANCE==null){
INSTANCE=new Singleton();
}
return INSTANCE;
}
}
在靜態(tài)方法上加鎖琢岩,相當(dāng)于對類對象加鎖
上述代碼等價(jià)于
package com.zcp.juc.single;
/**
* @author zcp
* @description
* @created by 2020-03-26 22:50
*/
public final class Singleton {
private static Singleton INSTANCE=null;
private Singleton(){
}
public static Singleton getInstance(){
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
}
線程安全問題解決了,還有什么問題呢师脂?
問題來了:T1線程先拿到鎖担孔,T2線程阻塞,T1線程同步代碼塊執(zhí)行完畢危彩,成功創(chuàng)建了對象攒磨,釋放了鎖,此時(shí)T2線程拿到鎖汤徽,再執(zhí)行if判斷娩缰,發(fā)現(xiàn)實(shí)例已經(jīng)被初始化,大家不覺得很麻煩嗎谒府?為什么不直接告訴T2對象已經(jīng)被創(chuàng)建了拼坎,直接獲取就是了浮毯,還要加一次鎖,大哥啊泰鸡,加鎖不要錢罢丁?
怎么辦呢盛龄?
那么傳說中雙重判斷來了,在加鎖前判斷一次INSTANCE是否等于null饰迹,不等于直接就返回實(shí)例了,這樣也不用再加鎖判斷了余舶。(也就是首次訪問需要同步啊鸭,而之后就沒有synchronized了),這樣做還有問題嗎匿值?
biao急霸啤?
go on..
package com.zcp.juc.single;
/**
* @author zcp
* @description
* @created by 2020-03-26 22:50
*/
public final 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;
}
}
什么問題呢挟憔?
看字節(jié)碼(重點(diǎn)看17~24行字節(jié)碼指令)
0: getstatic #2 // 獲取靜態(tài)變量INSTANCE
3: ifnonnull 37 //判斷INSTANCE是不是Null 如果不是Null就跳轉(zhuǎn)執(zhí)行37行
6: ldc #3 // 獲得了類對象
8: dup //把類對象的引用指針復(fù)制了一份
9: astore_0 //然后臨時(shí)存儲了復(fù)制的一份钟些,是為了將來解鎖用
10: monitorenter //開始執(zhí)行同步代碼塊
11: getstatic #2 // 拿到靜態(tài)變量
14: ifnonnull 27 //如果不為Null,執(zhí)行27行
17: new #3 // 為Null绊谭,繼續(xù)執(zhí)行政恍,創(chuàng)建對象,將對象的引用入棧
20: dup // 復(fù)制一份這個(gè)對象的引用(引用地址)
21: invokespecial #4 // 利用對象的引用來調(diào)用構(gòu)造方法(根據(jù)引用地址調(diào)用)
24: putstatic #2 // 原來的這一份的引用對應(yīng)賦值操作龙誊,把他賦值給靜態(tài)變量
27: aload_0 //把臨時(shí)存儲的類對象取出來
28: monitorexit //解鎖抚垃,退出同步代碼塊
29: goto 37 //跳轉(zhuǎn)到37行
32: astore_1
33: aload_0
34: monitorexit
35: aload_1
36: athrow
37: getstatic #2 // 獲取靜態(tài)變量
40: areturn //返回結(jié)果
Exception table:
from to target type
11 29 32 any
32 35 32 any
_
17: new #3 // 創(chuàng)建對象,將對象的引用入棧 new Singleton()
20: dup // 復(fù)制一份這個(gè)對象的引用(引用地址)
21: invokespecial #4 // 利用對象的引用來調(diào)用構(gòu)造方法(根據(jù)引用地址調(diào)用)
24: putstatic #2 // 利用一份對象引用賦值給static Instance
jvm虛擬機(jī)在執(zhí)行時(shí)有可能做優(yōu)化(指令重排序優(yōu)化)趟大,也就是可能先執(zhí)行24,再執(zhí)行21铣焊,那么會(huì)導(dǎo)致什么問題呢逊朽?synchronized只可能保證同步代碼塊內(nèi)原子性(注:synchronized代碼塊內(nèi)的代碼仍可能發(fā)生有序性問題,即指令重排序)曲伊,但是無法保證外面if判斷叽讳。啥意思呢?
當(dāng)T1線程執(zhí)行到同步代碼塊內(nèi)坟募,發(fā)生了指令重排序岛蚤,先調(diào)用了24行的指令,將對象的引用賦值給了static Instance懈糯,那么此時(shí)T2執(zhí)行到同步代碼塊外面的if判斷涤妒,就會(huì)發(fā)現(xiàn)Instance不為Null,就繼續(xù)執(zhí)行返回,可返回的時(shí)候赚哗,T1還未將構(gòu)造方法初始完畢她紫。
總結(jié):
- 關(guān)鍵在于0:getstatic在monitor外面硅堆,就好像不守規(guī)矩的人,他可以越過monitor讀取Instance變量的值
- T1還未完全將構(gòu)造方法初始完畢贿讹,如果構(gòu)造方法內(nèi)要執(zhí)行很多初始化操作渐逃,那么T2拿走的將是一個(gè)未初始化完畢的實(shí)例
- 對Instance使用volatile修飾即可,可以禁止指令重排序民褂。
最終完全的代碼:
package com.zcp.juc.single;
/**
* @author zcp
* @description
* @created by 2020-03-26 22:50
*/
public final class Singleton {
private static volatile Singleton INSTANCE=null;
private Singleton(){
}
public static Singleton getInstance(){
if(INSTANCE==null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}