本篇內(nèi)容主要講解“怎么實現(xiàn)Java單例模式”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“怎么實現(xiàn)Java單例模式”吧!
創(chuàng)新互聯(lián)服務(wù)項目包括常州網(wǎng)站建設(shè)、常州網(wǎng)站制作、常州網(wǎng)頁制作以及常州網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,常州網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到常州省份的部分城市,未來相信會繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
單例模式(Singleton Pattern)是一個比較簡單的設(shè)計模式,屬于創(chuàng)建型模式。其定義為
確保某一個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例
在系統(tǒng)中,單例模式要求一個單例對象只能有一個實例,這類對象如果有多個實例就可能會產(chǎn)生一些問題,如:資源消耗過多,處理結(jié)果不一致等,一般單例會有以下使用場景
生成唯一 序列號
整個項目的共享訪問點或共享數(shù)據(jù),如Web頁面計數(shù)器
創(chuàng)建一個對象實例需要消耗過多資源,如I/O和數(shù)據(jù)庫連接等
在Java中,一個單例對象在一個JVM中,只會有一個實例存在。以下是單例模式結(jié)構(gòu)圖
有一個該單例對象的靜態(tài)成員變量
私有的構(gòu)造函數(shù),只能被自身實例化
提供一個靜態(tài)公共方法實例化對象,并訪問該對象實例
單例模式有兩種實現(xiàn)方式:
餓漢式
懶漢式(延遲加載)
/** * 餓漢式單例 */ public class Singleton1 { // 靜態(tài)成員變量,在靜態(tài)初始化時便實例化對象 private static final Singleton1 singleton = new Singleton1(); // 構(gòu)造私有 private Singleton1(){ } public static Singleton1 getSingletonInstance(){ return singleton; } }
餓漢式指的是,在類加載的時候便實例化了該單例對象,不管有沒有使用,先創(chuàng)建了再說。這種方式是可以保證線程安全的,但是如果該對象一直沒有被使用,就浪費了空間資源。
但是對于一些空間占用較大、或是只在某些特定場景才使用的單例,我們會想要在第一次使用的時候才去實例化,這時,就需要懶漢式的延遲加載
/** * 懶漢式單例(非線程安全) */ public class Singleton2 { private static Singleton2 singleton; private Singleton2(){} // 獲取實例 public static Singleton2 getSingletonInstance(){ if(singleton == null){ singleton = new Singleton2(); } return singleton; } }
從上面代碼可以看出,懶漢式與餓漢式在于單例對象的創(chuàng)建時機(jī)。餓漢式是在類加載時便實例化對象,調(diào)用時無須判斷直接返回即可;而懶漢式是在第一次調(diào)用時實例化,并且每次調(diào)用都需要判斷是否已經(jīng)實例化
但是上面的這種方式在多線程下是不安全的,多個線程同時訪問getSingletonInstance()
時,可能會創(chuàng)建多個實例,便不再是單例了。那怎么解決線程安全的問題呢?首先我們可能會想到對getSingletonInstance()
方法加上synchronized關(guān)鍵字
/** * 懶漢式單例(synchronized關(guān)鍵字線程安全) */ public class Singleton3 { private static Singleton3 singleton; private Singleton3(){} // 獲取實例 public static synchronized Singleton3 getSingletonInstance(){ if(singleton == null){ singleton = new Singleton3(); } return singleton; } }
getSingletonInstance()
方法加上了同步鎖,增加了獲取實例的時間消耗,且在多線程下可能會發(fā)生阻塞。但其實我們并不想每次獲取實例的時候都去加上鎖,只是想在第一次調(diào)用創(chuàng)建對象時保證線程安全即可
對getSingletonInstance()
方法加上鎖,確實能保證線程安全,卻存在性能的問題。是不是要必要對整個方法加鎖?還是當(dāng)我檢查到實例還沒有創(chuàng)建,才去同步
**雙重校驗鎖(double-checked locking,DCL)**是能解決這個問題的
/** * 雙重校驗鎖(double-checked locking,DCL) */ public class Singleton4 { /** * 成員變量這里會加上關(guān)鍵字 volatile,目的是為了防止指令重排序 */ private static volatile Singleton4 singleton; private Singleton4(){} // 獲取實例 public static Singleton4 getSingletonInstance(){ // 第一次校驗,沒有實例化才進(jìn)入同步代碼塊 if(singleton == null){ synchronized (Singleton4.class){ // 進(jìn)入同步代碼塊后,再判斷,如果為空才創(chuàng)建實例 if(singleton == null){ singleton = new Singleton4(); } } } return singleton; } }
不對方法加上鎖,只對創(chuàng)建實例的代碼加鎖即可。方法中會有兩次判空的操作,第一次是為了不必要的同步,為null才進(jìn)入同步代碼塊,第二次是進(jìn)入同步代碼塊后判斷為null才創(chuàng)建實例
注意:這里的成員變量加上了volatile關(guān)鍵字
使用volatile可以保證數(shù)據(jù)的可見性,不過synchronized也是能保證同步數(shù)據(jù)的可見性的,這里使用volatile更多的目的是為了禁止Java指令重排序
/** * 靜態(tài)內(nèi)部類 */ public class Singleton5 { private Singleton5(){} // 獲取實例 public static Singleton5 getSingletonInstance(){ return SingletonHolder.SINGLETON; } /** * 內(nèi)部類,JVM在類加載的時候,是互斥的,可以保證線程安全 */ private static class SingletonHolder{ private static final Singleton5 SINGLETON = new Singleton5(); } }
JVM在類加載的時候是會保證數(shù)據(jù)同步的,我們可以通過內(nèi)部類來創(chuàng)建單例對象。第一次加載Singleton5時并不會加載內(nèi)部類,不去使用內(nèi)部類的時候,該內(nèi)部類就不會加載。只有第一次調(diào)用getSingletonInstance()
方法,會去加載內(nèi)部類并實例化單例對象,這樣就可以做到延遲加載和線程安全了
使用枚舉方式來實現(xiàn)單例是非常簡潔的,支持序列化機(jī)制,絕對防止多次實例化
/** * 枚舉方式 */ public enum Singleton6 { /** * 枚舉方式實現(xiàn)單例 */ SINGLETON; public void handle() { // to do something } }
該單例的使用方法
public class SingletonDemo { @Test public void test(){ // 枚舉方式 Singleton6 singleton = Singleton6.SINGLETON; singleton.handle(); } }
到此,相信大家對“怎么實現(xiàn)Java單例模式”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!