深度解析單例模式简肴,反射和序列化之后的單例模式

對 volatile 變量的寫操作晃听,不允許和它之前的讀寫操作打亂順序;
對 volatile 變量的讀操作砰识,不允許和它之后的讀寫亂序能扒。

public class Single {
private static volatile Single s= null;  
private Single(){}//私有構(gòu)造方法,避免外部創(chuàng)建實(shí)例
public static Single getInstance() {  
    Single st = s;  // 在這里創(chuàng)建臨時(shí)變量
    if (st== null) {
          synchronized (Single.class) {//靜態(tài)同步函數(shù)的鎖是(類.class)
                st= s;
                if (st== null) {
                      s=st= new Single();  
                }
            }
     }
      return st;  // 注意這里返回的是臨時(shí)變量
}

這里為什么需要再定義一個(gè)臨時(shí)變量st?通過前面的對 volatile 關(guān)鍵字作用解釋可知辫狼,訪問 volatile 變量初斑,需要保證一些執(zhí)行順序,所以的開銷比較大膨处。這里定義一個(gè)臨時(shí)變量见秤,在 s 不為空的時(shí)候(這是絕大部分的情況),只要在開始訪問一次 volatile 變量真椿,返回的是臨時(shí)變量鹃答。如果沒有此臨時(shí)變量,則需要訪問兩次突硝,而降低了效率测摔。通過這樣修改以后,這樣能提高 25% 的性能。wiki

關(guān)于單例模式锋八,還有一個(gè)更有趣的實(shí)現(xiàn)浙于,采用靜態(tài)內(nèi)部類,它能夠延遲初始化(lazy initialization)挟纱,并且多線程安全羞酗,還能保證高性能,如下:

 class Singleton{
      private Singleton(){}//私有構(gòu)造方法,避免外部創(chuàng)建實(shí)例
      private static class SingletonHolder
         {
          public static final Singleton instance= new Singleton();
         }
      public static Singleton getInstance()
         {
          return SingletonHolder.instance;
        }
}

利用內(nèi)部類延遲初始化樊销,這里是利用了 Java 的語言特性整慎,內(nèi)部類只有在使用的時(shí)候,才回去加載围苫,從而初始化內(nèi)部靜態(tài)變量裤园。關(guān)于線程安全,這是 Java 運(yùn)行環(huán)境自動(dòng)給你保證的剂府,在加載的時(shí)候拧揽,會自動(dòng)隱形的同步。在訪問對象的時(shí)候腺占,不需要同步 Java 虛擬機(jī)又會自動(dòng)給你取消同步淤袜,所以效率非常高。

在反射的作用下衰伯,單例結(jié)構(gòu)是會被破壞的铡羡,測試代碼如下所示

package test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import singleton.LazySingleton2;
/**
 * @author zhengrongjun
 */
public class LazySingleton2Test {
    public static void main(String[] args) {
        //創(chuàng)建第一個(gè)實(shí)例
        LazySingleton2 instance1 = LazySingleton2.getInstance();
        //通過反射創(chuàng)建第二個(gè)實(shí)例
        LazySingleton2 instance2 = null;
        try {
            Class<LazySingleton2> clazz = LazySingleton2.class;
            Constructor<LazySingleton2> cons = clazz.getDeclaredConstructor();
            cons.setAccessible(true);
            instance2 = cons.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //檢查兩個(gè)實(shí)例的hash值
        System.out.println("Instance 1 hash:" + instance1.hashCode());
        System.out.println("Instance 2 hash:" + instance2.hashCode());
    }
}

輸出如下

Instance 1 hash:1694819250
Instance 2 hash:1365202186

根據(jù)哈希值可以看出,反射破壞了單例的特性意鲸,因此懶漢式V3版誕生了:

package singleton;
public class LazySingleton3 {
    private static boolean initialized = false;
    private LazySingleton3() {
        synchronized (LazySingleton3.class) {
            if (initialized == false) {
                initialized = !initialized;
            } else {
                throw new RuntimeException("單例已被破壞");
            }
        }
    }
    static class SingletonHolder {
        private static final LazySingleton3 instance = new LazySingleton3();
    }
    public static LazySingleton3 getInstance() {
        return SingletonHolder.instance;
    }
}

此時(shí)再運(yùn)行一次測試類烦周,出現(xiàn)如下提示

java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at test.LazySingleton3Test.main(LazySingleton3Test.java:21)
Caused by: java.lang.RuntimeException: 單例已被破壞
    at singleton.LazySingleton3.<init>(LazySingleton3.java:12)
    ... 5 more
Instance 1 hash:359023572

這里就保證了,反射無法破壞其單例特性.

在分布式系統(tǒng)中怎顾,有些情況下你需要在單例類中實(shí)現(xiàn) Serializable 接口读慎。這樣你可以在文件系統(tǒng)中存儲它的狀態(tài)并且在稍后的某一時(shí)間點(diǎn)取出。讓我們測試這個(gè)懶漢式V3版在序列化和反序列化之后是否仍然保持單例槐雾。
先將

public class LazySingleton3

修改為

public class LazySingleton3 implements Serializable 

上測試類如下

package test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import singleton.LazySingleton3;
public class LazySingleton3Test {
    public static void main(String[] args) {
        try {
            LazySingleton3 instance1 = LazySingleton3.getInstance();
            ObjectOutput out = null;
            out = new ObjectOutputStream(new FileOutputStream("filename.ser"));
            out.writeObject(instance1);
            out.close();
            //deserialize from file to object
            ObjectInput in = new ObjectInputStream(new FileInputStream("filename.ser"));
            LazySingleton3 instance2 = (LazySingleton3) in.readObject();
            in.close();
            System.out.println("instance1 hashCode=" + instance1.hashCode());
            System.out.println("instance2 hashCode=" + instance2.hashCode());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

輸出如下

instance1 hashCode=2051450519
instance2 hashCode=1510067370

顯然夭委,我們又看到了兩個(gè)實(shí)例類。為了避免此問題募强,我們需要提供 readResolve() 方法的實(shí)現(xiàn)株灸。readResolve()代替了從流中讀取對象。這就確保了在序列化和反序列化的過程中沒人可以創(chuàng)建新的實(shí)例钻注。

因此蚂且,我們提供懶漢式V4版代碼如下

package singleton;
import java.io.Serializable;
public class LazySingleton4 implements Serializable {
    private static boolean initialized = false;
    private LazySingleton4() {
        synchronized (LazySingleton4.class) {
            if (initialized == false) {
                initialized = !initialized;
            } else {
                throw new RuntimeException("單例已被破壞");
            }
        }
    }
    static class SingletonHolder {
        private static final LazySingleton4 instance = new LazySingleton4();
    }
    public static LazySingleton4 getInstance() {
        return SingletonHolder.instance;
    }
    private Object readResolve() {
        return getInstance();
    }
}

此時(shí),在運(yùn)行測試類幅恋,輸出如下

instance1 hashCode=2051450519
instance2 hashCode=2051450519

這表示此時(shí)已能保證序列化和反序列化的對象是一致的.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末杏死,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌淑翼,老刑警劉巖腐巢,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異玄括,居然都是意外死亡冯丙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門遭京,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胃惜,“玉大人,你說我怎么就攤上這事哪雕〈常” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵斯嚎,是天一觀的道長利虫。 經(jīng)常有香客問我,道長堡僻,這世上最難降的妖魔是什么糠惫? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮钉疫,結(jié)果婚禮上硼讽,老公的妹妹穿的比我還像新娘。我一直安慰自己牲阁,他們只是感情好理郑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咨油,像睡著了一般。 火紅的嫁衣襯著肌膚如雪柒爵。 梳的紋絲不亂的頭發(fā)上役电,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天,我揣著相機(jī)與錄音棉胀,去河邊找鬼法瑟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛唁奢,可吹牛的內(nèi)容都是我干的霎挟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼麻掸,長吁一口氣:“原來是場噩夢啊……” “哼酥夭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤熬北,失蹤者是張志新(化名)和其女友劉穎疙描,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體讶隐,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡起胰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巫延。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片效五。...
    茶點(diǎn)故事閱讀 40,013評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖炉峰,靈堂內(nèi)的尸體忽然破棺而出畏妖,到底是詐尸還是另有隱情,我是刑警寧澤讲冠,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布瓜客,位于F島的核電站,受9級特大地震影響竿开,放射性物質(zhì)發(fā)生泄漏谱仪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一否彩、第九天 我趴在偏房一處隱蔽的房頂上張望疯攒。 院中可真熱鬧,春花似錦列荔、人聲如沸敬尺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽砂吞。三九已至,卻和暖如春崎溃,著一層夾襖步出監(jiān)牢的瞬間蜻直,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工袁串, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留概而,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓囱修,卻偏偏與公主長得像赎瑰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子破镰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Java8張圖 11餐曼、字符串不變性 12压储、equals()方法、hashCode()方法的區(qū)別 13晋辆、...
    Miley_MOJIE閱讀 3,707評論 0 11
  • 從三月份找實(shí)習(xí)到現(xiàn)在渠脉,面了一些公司,掛了不少瓶佳,但最終還是拿到小米芋膘、百度、阿里霸饲、京東为朋、新浪、CVTE厚脉、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,251評論 11 349
  • 等你熬過那些孤獨(dú)無助的時(shí)刻习寸,你才會發(fā)現(xiàn),原來自己并沒有想象中那么脆弱傻工。原來一個(gè)人霞溪,也可以活成千軍萬馬的模樣。
    叮咚_b22d閱讀 189評論 1 0
  • 1.創(chuàng)建或者打開數(shù)據(jù)庫 2.增刪改 oc封裝方法 注意 sqlite3_exec()可以執(zhí)行任何SQL語句中捆,比如創(chuàng)...
    動(dòng)感新勢力fan閱讀 1,484評論 2 0
  • 生命是一艘行走的船鸯匹, 載著出海時(shí)的喜悅, 載著深海中的艱難泄伪, 載著歸海時(shí)的苦盡甘來殴蓬。 生命, 從來不曾停歇蟋滴。 生命...
    劉劉劉劉劉大地閱讀 342評論 0 0