Java常用设计模式————单例模式
單例模式簡介
90%以上的設計模式都或多或少的應用了接口和抽象類,而單例比較特殊,并沒有接口的應用。
單例Singleton指僅僅被實例化一次的類。通常被用來代表那些本質上唯一的系統組件。————《Effective Java》
數據庫連接獲取類的對象可以是單例的。
單例的意思就是在內存中只有一個對象。而單例和static有是有區別的,static是用來修飾類中的成員變量和成員方法的,而單例則屬于對象的層面。
單例的實現
思考:如何在內存中只有一個對象?——不能讓外界隨意的實例化(new)。封裝思想中有一個叫“屬性私有化,方法公開化”的概念,在需要單例的類中自己去實例化。這就是單例的兩個條件:不能被外界實例化;根據封裝的特征屬性私有化,方法公開化。
外界不能實例化,實現的方式就是將需要設置為單例的類(以下簡稱:單例類)的構造器設置為private。
注意:如果電腦上安裝了多個虛擬機的話,那么實際上并不是真正意義上的單例,而是每個虛擬機會有一個單例。
懶漢單例模式:
懶漢單例模式在第一次使用對象的時候才會去創建對象,但最簡單的懶漢單例存在多線程安全問題,依然有可能創建多個實例。
/*** 懶漢單例模式Demo<br>懶漢單例是在運行時調用的一種行為。* 單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。<br>* 這種類型的設計模式屬于創建型模式,它提供了一種創建對象的最佳方式。<br>* 這種模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。<br>* 這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。<br>* * 類名:LazySingleton<br>* 作者: mht<br>* 日期: 2018年3月18日-下午8:41:01<br>*/ public class LazySingleton {/** 屬性私有化 */private static LazySingleton instance = null;/** 構造器私有化 */private LazySingleton() { }/** 公開獲取單例對象 */public static LazySingleton getInstance() {if (instance == null) {instance = new LazySingleton();}return instance;} }餓漢單例模式:
餓漢單例會在類加載的時候即實例化對象,這與懶漢單例的創建時機截然不同,不存在線程安全問題,
但缺點是需要提前占用內存資源。
/*** 類名:EagerSingleton<br>* 作者: mht<br>* 日期: 2018年3月18日-下午8:41:01<br>*/ public class EagerSingleton {/** 使用靜態常量*/private static final EagerSingleton instance = new EagerSingleton();private EagerSingleton() {System.out.println("這是構造器:LazySingleton()");}public static EagerSingleton getInstance() {return instance;}public void doSomething() {System.out.println("EagerSingleton is doing something now ...");} }懶漢+餓漢(靜態內部類):
該方法通過私有靜態內部類來實現單實例延遲加載,因為靜態內部類不會因為外部類的加載而加載,所以可以在第一次使用靜態類的時候才執行加載。
優點是:不僅可以達到懶漢式的延遲加載,使類的加載速度提升,避免一開始占用過多內存,又具備線程安全性,無需判斷,性能上也會更快一些。
雙重檢查-單例模式(線程安全的懶漢式單例):
雙重檢查單例模式 使用雙重檢查同步延遲加載來創建單例的做法是一個非常優秀的做法, 其不但保證了單例(線程安全),而且切實提高了程序的運行效率。
實現原理:
Java語言提供了一種較synchronized稍弱的同步機制,volatile,用來確保將變量的更新操作通知到其他線程。當把變量聲明為volatile類型后,編譯器與運行時都會注意到這個變量是共享的,因此不會將該變量上的操作與其他內存操作一起重排序。volatile變量不會被緩存在寄存器或者對其他處理器不可見的地方,因此在讀取volatile類型的變量時總會返回最新寫入的值。
getInstance()方法描述:
為了在保證單例的前提下提高運行效率,我們需要對單例對象dcs進行第二次檢查,目的是避開過多的同步(因為這里的同步只需在第一次創建實例時才同步,一旦創建成功,以后獲取實例時就不需要同步獲取鎖了)。這種做法無疑是優秀的,但是我們必須注意一點:必須使用volatile關鍵字修飾單例對象。
public class DoubleCheckSingleton {// 使用volatile關鍵字防止重排序,因為 new Instance()是一個非原子操作。private static volatile DoubleCheckSingleton dcs;private DoubleCheckSingleton() {}public static DoubleCheckSingleton getInstance() {if (dcs == null) {synchronized (DoubleCheckSingleton.class) {// 只需在第一次創建實例時才同步if (dcs == null) {dcs = new DoubleCheckSingleton();}}}return dcs;} }注冊單例模式:
public class RegisterSingleton<T> {/* 注冊表 */private static Map<String, Object> regMap = new HashMap<>();// 類加載過程中,靜態初始化static{Connection conn = new Connection();UserService us = new UserService();// 初始化注冊表regMapregMap.put(conn.getClass().getName(), conn);regMap.put(us.getClass().getName(), us);}private RegisterSingleton() { }public synchronized static Object getInstance(String key) {if (key == null) return null;try {if (regMap.get(key) == null) {// 這里注意,此Demo是以類名作為key傳入map中的,因此,這里的傳值也是相關方法,通過類名找到類,然后在進行自動實例化regMap.put(key, Class.forName(key).newInstance());}return regMap.get(key);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}return null;} }測試:
package design.pattern;public class testSingleton {public static void main(String[] args) {UserService us = (UserService) RegisterSingleton.getInstance(UserService.class.getName());Connection conn1 = (Connection) RegisterSingleton.getInstance(Connection.class.getName());Connection conn2 = (Connection) RegisterSingleton.getInstance(Connection.class.getName());System.out.println(us);System.out.println(conn1);System.out.println(conn2);System.out.println(conn1 == conn2);} }運行結果:
design.pattern.UserService@15db9742 design.pattern.Connection@6d06d69c design.pattern.Connection@6d06d69c true對單例模式應用的理解:
在spring框架中大多數對象的使用都是單例模式的,比如獲取用戶User對象的方法類UserDao和UserService都是單例的,包括controller,他們默認都單例的。因為實際上我們真正需要多個對象的是User而并不是獲取User的方法類。
這也就充分解釋了Spring注解中的@Autowired自動注入對象的實現方式。細心的我們也都可以發現,這些自動注入的對象一般都是實現某種業務的中間類對象,而并不是最終我們需要的對象,因此,為了避免頻繁的創建這些“中間件”對象占用不必要的內存空間,都是以單例的形式來創建的。另外,數據庫連接對象Connection也一定是單例的。
單例模式的誤解:
單例會導致線程阻塞嗎?不會的。
舉個生活中的例子:飯店只有一個菜譜,但是有多個廚師,多個廚師共享這份菜譜,并同時做一道菜,彼此之間是沒有影響的;老師在黑板上寫下一個數學題,同學們在下面計算結果,黑板就是內存,數學題就是單例對象,同學們就是各個線程,計算過程之間是沒有影響的。
單例模式的優點:
1.內存中只有一個對象,節省內存空間
2.避免頻繁的創建和銷毀對象,可以提高性能
3.避免對共享資源的多重占用,簡化訪問
4.為整個系統提供一個全局訪問點
單例模式的應用場景:
在計算機系統中,線程池、緩存、日志對象、對話框、打印機、顯卡驅動程序對象常被設計為單例的。實際上,這些應用都或多或少具有資源管理器的功能。
其核心在于為整個系統提供一個唯一的實例,其應用場景包括但不僅限于一下幾種:
1.有狀態的工具類
2.頻繁訪問數據庫或文件的對象
參考:
《徹頭徹尾理解單例模式以及其在多線程環境中的應用》
總結
以上是生活随笔為你收集整理的Java常用设计模式————单例模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 两个摄像头合成一路_教你把一个摄像机添加
- 下一篇: oracle查询结果存入临时表,Orac