您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“怎么實現(xiàn)Java單例模式”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“怎么實現(xiàn)Java單例模式”吧!
單例模式(Singleton Pattern)是一個比較簡單的設計模式,屬于創(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)建時機。餓漢式是在類加載時便實例化對象,調(diào)用時無須判斷直接返回即可;而懶漢式是在第一次調(diào)用時實例化,并且每次調(diào)用都需要判斷是否已經(jīng)實例化
但是上面的這種方式在多線程下是不安全的,多個線程同時訪問getSingletonInstance()
時,可能會創(chuàng)建多個實例,便不再是單例了。那怎么解決線程安全的問題呢?首先我們可能會想到對getSingletonInstance()
方法加上synchronized關鍵字
/** * 懶漢式單例(synchronized關鍵字線程安全) */ 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()
方法加上鎖,確實能保證線程安全,卻存在性能的問題。是不是要必要對整個方法加鎖?還是當我檢查到實例還沒有創(chuàng)建,才去同步
**雙重校驗鎖(double-checked locking,DCL)**是能解決這個問題的
/** * 雙重校驗鎖(double-checked locking,DCL) */ public class Singleton4 { /** * 成員變量這里會加上關鍵字 volatile,目的是為了防止指令重排序 */ private static volatile Singleton4 singleton; private Singleton4(){} // 獲取實例 public static Singleton4 getSingletonInstance(){ // 第一次校驗,沒有實例化才進入同步代碼塊 if(singleton == null){ synchronized (Singleton4.class){ // 進入同步代碼塊后,再判斷,如果為空才創(chuàng)建實例 if(singleton == null){ singleton = new Singleton4(); } } } return singleton; } }
不對方法加上鎖,只對創(chuàng)建實例的代碼加鎖即可。方法中會有兩次判空的操作,第一次是為了不必要的同步,為null才進入同步代碼塊,第二次是進入同步代碼塊后判斷為null才創(chuàng)建實例
注意:這里的成員變量加上了volatile關鍵字
使用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)單例是非常簡潔的,支持序列化機制,絕對防止多次實例化
/** * 枚舉方式 */ 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單例模式”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關內(nèi)容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。