多线程--单例模式
單例模式是23種設計模式中比較簡單的模式之一,本博客較為詳細的梳理了該設計模式,并實現該模式。
問題由來 : 我們為什么需要單例模式?
在許多時候,一個系統只需要一個全局對象,這樣有利于我們協調系統的整體行為。比如在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,然后服務進程中的其他對象再通過這個單例對象獲取這些配置信息。這樣做可以簡化對系統的管理,并且可以避免出現不一致的狀況。
如何解決 : 單例模式的概念
在Java中,單例模式就是確保一個類中只有一個實例,并且它可以提供一個全局的訪問點以訪問該實例。它可以為整個系統提供一個全局訪問點,避免了(不必要的)頻繁的創建與銷毀對象,可以提高系統性能,節省資源,并且便于整體把控系統。
概念模型 : 在java中建立單例模式的模型
在Java中,萬物皆對象,那么,如何將單例模式的概念抽象為Java中的對象呢?
將單例模式看作一類對象,則這類對象需要有三大要素:
- 指向自己(唯一)實例的私有靜態引用 :因為只有一個唯一的實例,所以用靜態引用
- 私有的構造方法 : 避免外部創建實例對象
- 公有的以自己(唯一)實例為返回值的靜態方法:提供一個訪問該實例的方法
類的實現 :
單例模式的實現有兩種比較經典的實現方法
單線程實現 :
餓漢模式 :
class Hunger_Thread{// 指向自己實例的私有靜態引用,// 在類加載時便new了一個對象(類只會加載一次,所以只會new一個對象)private static Hunger_Thread hunger = new Hunger_Thread();// 私有構造方法private Hunger_Thread(){};// 以自己實例為返回值的靜態公有方法public static Hunger_Thread getHunger(){return hunger;}}懶漢模式 :
class Lazy_Thread{// 指向自己實例的私有靜態引用private static Lazy_Thread lazy;// 私有構造方法private Lazy_Thread(){}// 以自己實例為返回的靜態公有方法public static Lazy_Thread getLazy(){if(lazy==null){lazy = new Lazy_Thread();}return lazy;} }多線程實現 :
多線程需要考慮線程安全問題 ,由于餓漢模式(不管是單線程還是多線程)只會在類加載時new對象,由于一個類在整個生命周期中只會被加載一次,因此該單例類只會創建一個實例,故它是線程安全的。
但懶漢模式會出現線程不安全的例子 :(就拿單線程的懶漢代碼為例)
假設有線程 t1,t2,這兩個線程都調用了getLazy()方法(lazy==null,t1先執行),t1執行到 if(lazy==null){...}(剛判斷完,還未創建實例)時,時間片用完了,此時該t2執行,t2完整的執行了該方法(已經創建了實例),又該t1執行了,此時它并不知道已經創建了實例,所以還會創建一個,這就與該模式的設計理念不符,也就是線程不安全。
解決方法也很簡單,加鎖就可以了
class Lazy_Thread{// 指向自己實例的私有靜態引用private static Lazy_Thread lazy;// 私有構造方法private Lazy_Thread(){}// 以自己實例為返回的靜態公有方法public static synchronized Lazy_Thread getLazy(){if(lazy==null){lazy = new Lazy_Thread();}return lazy;} }這樣可以解決線程安全問題,但是效率低下,鎖的粒度太大。下面的代碼段與上面的相比沒什么效率的提升。
class Lazy_Thread{// 指向自己實例的私有靜態引用private static Lazy_Thread lazy;// 私有構造方法private Lazy_Thread(){}// 以自己實例為返回的靜態公有方法public static Lazy_Thread getLazy(){synchronized (Lazy_Thread.class){if(lazy==null){lazy = new Lazy_Thread();}}return lazy;} }一般,我們會使用雙重檢查來實現線程安全,這是一種比較高效的做法。
class Lazy_Thread{// 要用 volatile 修飾,防止指令重排序private static volatile Lazy_Thread lazy;private Lazy_Thread(){}// 雙重檢查實現線程安全public static Lazy_Thread getLazy(){if(lazy==null){// 只有第一次創建實例時才加鎖synchronized (Lazy_Thread.class){if(lazy==null){lazy = new Lazy_Thread();}}}return lazy;} }為什么要用 volatile 修飾 lazy ?
?new AnyClass();是一個非原子操作 ,大致會分為這三步
如果發生了指令重排序,則有可能把步驟3放在步驟2之前執行(lazy不為null,但是它沒有初始化,它就是一個殘缺的對象)。
假設線程t1在執行new的時候發生了指令重排序,new的步驟變成了1,3,2,而t1在執行完3后時間片剛好用完(此時 lazy 就是一個殘缺的對象)這時,t2進入線程,判斷 lazy != null 之間返回的殘缺的對象,這也是線程不安全的!
總結
- 上一篇: Opening My World——ES
- 下一篇: HALCON示例程序max_connec