1. 程式人生 > >JAVA單例模式:就是把構造方法弄成私有的

JAVA單例模式:就是把構造方法弄成私有的

一.問題引入

  偶然想想到的如果把Java的構造方法弄成private,那裡面的成員屬性是不是隻有通過static來訪問呢;如果構造方法是private的話,那麼有什麼好處呢;如果構造方法是private的話,會不更好的封裝該內呢?我主要是應用在使用普通類模擬列舉型別裡,後來發現這就是傳說中的單例模式。建構函式弄成private 就是單例模式,即不想讓別人用new 方法來建立多個物件,可以在類裡面先生成一個物件,然後寫一個public static方法把這個物件return出去。(eg:public 類名 getInstancd(){return 你剛剛生成的那個類物件;}),用static是因為你的建構函式是私有的,不能產生物件,所以只能用類名呼叫,所有隻能是靜態函式。成員變數也可以寫getter/setter供外界訪問的。如果誰要用這個類的例項就用有興趣的讀者參看我的這一篇博文http://www.cnblogs.com/hxsyl/archive/2013/03/18/2966360.html。

  第一個程式碼不是單例模式,也就是說不一定只要構造方法是private的就是單例模式。

View Code View Code

二.單例模式概念及特點

  java中單例模式是一種常見的設計模式,單例模式分三種:懶漢式單例、餓漢式單例、登記式單例三種。
  單例模式有一下特點:
  1、單例類只能有一個例項。
  2、單例類必須自己自己建立自己的唯一例項。
  3、單例類必須給所有其他物件提供這一例項。

  單例模式確保某個類只有一個例項,而且自行例項化並向整個系統提供這個例項。在計算機系統中,執行緒池、快取、日誌物件、對話方塊、印表機、顯示卡的驅動程式物件常被設計成單例。這些應用都或多或少具有資源管理器的功能。每臺計算機可以有若干個印表機,但只能有一個Printer Spooler,以避免兩個列印作業同時輸出到印表機中。每臺計算機可以有若干通訊埠,系統應當集中管理這些通訊埠,以避免一個通訊埠同時被兩個請求同時呼叫。總之,選擇單例模式就是為了避免不一致狀態,避免政出多頭。

  正是由於這個特 點,單例物件通常作為程式中的存放配置資訊的載體,因為它能保證其他物件讀到一致的資訊。例如在某個伺服器程式中,該伺服器的配置資訊可能存放在資料庫或 檔案中,這些配置資料由某個單例物件統一讀取,服務程序中的其他物件如果要獲取這些配置資訊,只需訪問該單例物件即可。這種方式極大地簡化了在複雜環境 下,尤其是多執行緒環境下的配置管理,但是隨著應用場景的不同,也可能帶來一些同步問題。

三.典型例題

  首先看一個經典的單例實現。

複製程式碼
 1 public class Singleton {
 2  
 3     private static Singleton uniqueInstance = null
; 4 5 6 7 private Singleton() { 8 9 // Exists only to defeat instantiation. 10 11 } 12 13 14 15 public static Singleton getInstance() { 16 17 if (uniqueInstance == null) { 18 19 uniqueInstance = new Singleton(); 20 21 } 22 23 return uniqueInstance; 24 25 } 26 27 // Other methods... 28 29 }
複製程式碼

  Singleton通過將構造方法限定為private避免了類在外部被例項化,在同一個虛擬機器範圍內,Singleton的唯一例項只能通過getInstance()方法訪問。(事實上,通過Java反射機制是能夠例項化構造方法為private的類的,那基本上會使所有的Java單例實現失效。此問題在此處不做討論,姑且掩耳盜鈴地認為反射機制不存在。)

  但是以上實現沒有考慮執行緒安全問題。所謂執行緒安全是指:如果你的程式碼所在的程序中有多個執行緒在同時執行,而這些執行緒可能會同時執行這段程式碼。如果每次執行結果和單執行緒執行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是執行緒安全的。或者說:一個類或者程式所提供的介面對於執行緒來說是原子操作或者多個執行緒之間的切換不會導致該介面的執行結果存在二義性,也就是說我們不用考慮同步的問題。顯然以上實現並不滿足執行緒安全的要求,在併發環境下很可能出現多個Singleton例項。

View Code View Code

執行結果:
  張孝祥
  張孝祥
  output message 張孝祥
  output message 張孝祥
  建立的是同一個例項
 
結論:由結果可以得知單例模式為一個面向物件的應用程式提供了物件惟一的訪問點,不管它實現何種功能,整個應用程式都會同享一個例項物件。

  其次,下面是單例的三種實現。    

    1.餓漢式單例類

  飛哥下面這個可以不加final,因為靜態方法只在編譯期間執行一次初始化,也就是隻會有一個物件。

複製程式碼
 1 //餓漢式單例類.在類初始化時,已經自行例項化 
 2  public class Singleton1 {
 3      //私有的預設構造子
 4      private Singleton1() {}
 5      //已經自行例項化 
 6      private static final Singleton1 single = new Singleton1();
 7      //靜態工廠方法 
 8      public static Singleton1 getInstance() {
 9          return single;
10      }
11  }
複製程式碼

    2.懶漢式單例類

  那個if判斷確保物件只建立一次。

複製程式碼
 1 //懶漢式單例類.在第一次呼叫的時候例項化 
 2  public class Singleton2 {
 3      //私有的預設構造子
 4      private Singleton2() {}
 5      //注意,這裡沒有final    
 6      private static Singleton2 single=null;
 7      //靜態工廠方法 
 8      public synchronized  static Singleton2 getInstance() {
 9           if (single == null) {  
10               single = new Singleton2();
11           }  
12          return single;
13      }
14  }
複製程式碼

     3.登記式單例類

複製程式碼
 1 import java.util.HashMap;
 2  import java.util.Map;
 3  //登記式單例類.
 4  //類似Spring裡面的方法,將類名註冊,下次從裡面直接獲取。
 5  public class Singleton3 {
 6      private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();
 7      static{
 8          Singleton3 single = new Singleton3();
 9          map.put(single.getClass().getName(), single);
10      }
11      //保護的預設構造子
12      protected Singleton3(){}
13      //靜態工廠方法,返還此類惟一的例項
14      public static Singleton3 getInstance(String name) {
15          if(name == null) {
16              name = Singleton3.class.getName();
17              System.out.println("name == null"+"--->name="+name);
18          }
19          if(map.get(name) == null) {
20              try {
21                  map.put(name, (Singleton3) Class.forName(name).newInstance());
22              } catch (InstantiationException e) {
23                  e.printStackTrace();
24              } catch (IllegalAccessException e) {
25                  e.printStackTrace();
26              } catch (ClassNotFoundException e) {
27                  e.printStackTrace();
28              }
29          }
30          return map.get(name);
31      }
32      //一個示意性的商業方法
33      public String about() {    
34          return "Hello, I am RegSingleton.";    
35      }    
36      public static void main(String[] args) {
37          Singleton3 single3 = Singleton3.getInstance(null);
38          System.out.println(single3.about());
39      }
40  }
複製程式碼

四.單例物件作配置資訊管理時可能會帶來的幾個同步問題
  

  1.在多執行緒環境下,單例物件的同步問題主要體現在兩個方面,單例物件的初始化和單例物件的屬性更新。

    本文描述的方法有如下假設:

    a. 單例物件的屬性(或成員變數)的獲取,是通過單例物件的初始化實現的。也就是說,在單例物件初始化時,會從檔案或資料庫中讀取最新的配置資訊。

    b. 其他物件不能直接改變單例物件的屬性,單例物件屬性的變化來源於配置檔案或配置資料庫資料的變化。

    1.1單例物件的初始化

      首先,討論一下單例物件的初始化同步。單例模式的通常處理方式是,在物件中有一個靜態成員變數,其型別就是單例型別本身;如果該變數為null,則建立該單例型別的物件,並將該變數指向這個物件;如果該變數不為null,則直接使用該變數。   

      這種處理方式在單執行緒的模式下可以很好的執行;但是在多執行緒模式下,可能產生問題。如果第一個執行緒發現成員變數為null,準備建立物件;這是第二 個執行緒同時也發現成員變數為null,也會建立新物件。這就會造成在一個JVM中有多個單例型別的例項。如果這個單例型別的成員變數在執行過程中變化,會 造成多個單例型別例項的不一致,產生一些很奇怪的現象。例如,某服務程序通過檢查單例物件的某個屬性來停止多個執行緒服務,如果存在多個單例物件的例項,就 會造成部分執行緒服務停止,部分執行緒服務不能停止的情況。

    1.2單例物件的屬性更新

      通常,為了實現配置資訊的實時更新,會有一個執行緒不停檢測配置檔案或配置資料庫的內容,一旦發現變化,就更新到單例物件的屬性中。在更新這些信 息的時候,很可能還會有其他執行緒正在讀取這些資訊,造成意想不到的後果。還是以通過單例物件屬性停止執行緒服務為例,如果更新屬性時讀寫不同步,可能訪問該 屬性時這個屬性正好為空(null),程式就會丟擲異常。

      下面是解決方法

複製程式碼
 1 //單例物件的初始化同步
 2 public class GlobalConfig {
 3     private static GlobalConfig instance = null;
 4     private Vector properties = null;
 5     private GlobalConfig() {
 6       //Load configuration information from DB or file
 7       //Set values for properties
 8     }
 9     private static synchronized void syncInit() {
10       if (instance == null) {
11         instance = new GlobalConfig();
12       }
13     }
14     public static GlobalConfig getInstance() {
15       if (instance == null) {
16         syncInit();
17       }
18       return instance;
19     }
20     public Vector getProperties() {
21       return properties;
22     }
23   }
複製程式碼

  這種處理方式雖然引入了同步程式碼,但是因為這段同步程式碼只會在最開始的時候執行一次或多次,所以對整個系統的效能不會有影響。

  單例物件的屬性更新同步。

  參照讀者/寫者的處理方式,設定一個讀計數器,每次讀取配置資訊前,將計數器加1,讀完後將計數器減1.只有在讀計數器為0時,才能更新資料,同時要阻塞所有讀屬性的呼叫。

  程式碼如下:

複製程式碼
 1 public class GlobalConfig {
 2  private static GlobalConfig instance;
 3  private Vector properties = null;
 4  private boolean isUpdating = false;
 5  private int readCount = 0;
 6  private GlobalConfig() {
 7    //Load configuration information from DB or file
 8       //Set values for properties
 9  }
10  private static synchronized void syncInit() {
11   if (instance == null) {
12    instance = new GlobalConfig();
13   }
14  }
15  public static GlobalConfig getInstance() {
16   if (instance==null) {
17    syncInit();
18   }
19   return instance;
20  }
21  public synchronized void update(String p_data) {
22   syncUpdateIn();
23   //Update properties
24  }
25  private synchronized void syncUpdateIn() {
26   while (readCount > 0) {
27    try {
28     wait();
29    } catch (Exception e) {
30    }
31   }
32  }
33  private synchronized void syncReadIn() {
34   readCount++;
35  }
36  private synchronized void syncReadOut() {
37   readCount--;
38   notifyAll();
39  }
40  public Vector getProperties() {
41   syncReadIn();
42   //Process data
43   syncReadOut();
44   return properties;
45  }
46   }
複製程式碼

  採用"影子例項"的辦法具體說,就是在更新屬性時,直接生成另一個單例物件例項,這個新生成的單例物件例項將從資料庫或檔案中讀取最新的配置資訊;然後將這些配置資訊直接賦值給舊單例物件的屬性。

複製程式碼
 1 public class GlobalConfig {
 2     private static GlobalConfig instance = null;
 3     private Vector properties = null;
 4     private GlobalConfig() {
 5       //Load configuration information from DB or file
 6       //Set values for properties
 7     }
 8     private static synchronized void syncInit() {
 9       if (instance = null) {
10         instance = new GlobalConfig();
11       }
12     }
13     public static GlobalConfig getInstance() {
14       if (instance = null) {
15         syncInit();
16       }
17       return instance;
18     }
19     public Vector getProperties() {
20       return properties;
21     }
22     public void updateProperties() {
23       //Load updated configuration information by new a GlobalConfig object
24       GlobalConfig shadow = new GlobalConfig();
25       properties = shadow.getProperties();
26     }
27   }
複製程式碼

注意:在更新方法中,通過生成新的GlobalConfig的例項,從檔案或資料庫中得到最新配置資訊,並存放到properties屬性中。上面兩個方法比較起來,第二個方法更好,首先,程式設計更簡單;其次,沒有那麼多的同步操作,對效能的影響也不大。

相關推薦

JAVA模式就是構造方法私有的

一.問題引入   偶然想想到的如果把Java的構造方法弄成private,那裡面的成員屬性是不是隻有通過static來訪問呢;如果構造方法是private的話,那麼有什麼好處呢;如果構造方法是private的話,會不更好的封裝該內呢?我主要是應用在使用普通類模擬列舉型別裡,後來發現這就是傳說中的單例模式。建

模式鎖版本

Singleton Pattern Ensure a class has only one instance,and provide a global point of access to it . 單例模式的幾個要點: 私有的構造器。禁止外部使用new關鍵字得到例

Java模式實現的幾種方法

package offer; public class Test02 { /** * 01 餓漢模式 執行緒安全 */ public static class Singleton{ private final static Singleton

java模式餓漢式、懶漢式、雙重校驗鎖、列舉、靜態內部類

一、餓漢式: /** * 餓漢式: * 不存在多執行緒同步問題,當類被載入時,初始化並分配記憶體空間; * 當類被解除安裝時,才釋放所佔記憶體,因此在某些特定條件下會耗費記憶體。 * * @author: Rodge * @time: 2018年10月4日 下午4:35:12 * @

面試一個模式,足以你秒

去面試(對,又去面試) 問:單例模式瞭解吧,來,拿紙和筆寫一下單例模式。 我心想,這TM不是瞧不起人嗎?我程式設計十年,能不知道單例模式。 答:(.net 平臺下)單例模式有兩種寫法: 第一種:飢餓模式,關鍵點,static readonly public static readonly Singleton

Java模式

class auth pre light java on() ack private gets package singleton; /** * 單例模式 * @author pengYi * */ public class Singleton { priva

java模式等一些程序的寫法....持續更新...

new tor zed bsp 更新 餓漢式 blog stat cto 一、單例模式的寫法: public class MyFactory { /** * 餓漢式 */ private static MyFactory instanc

java 模式

多線程安全 except detail 追加 earch 繼承 好處 config 什麽是 單例模式(Singleton)也叫單態模式,是設計模式中最為簡單的一種模式,甚至有些模式大師都不稱其為模式,稱其為一種實現技巧,因為設計模式講究對象之間的關系的抽象,而單例模式只有自

java模式的心得

開發人員 性能 文章 人員 外部 訪問 單例 鎖定 初始化   由於設計模式對於java高級開發人員來說是非常重要的,網上也有很多關於設計模式的文章,博客等。所以,首先我對相對簡單的單例模式做一個簡單的總結。   一、實現方式   單例模式的實現方式有3種,分別是餓漢式

Java模式深入詳解

protected test 異常 except while 深入 bject his 不一致 Java單例模式深入詳解 一.問題引入   偶然想想到的如果把Java的構造方法弄成private,那裏面的成員屬性是不是只有通過static來訪問呢;如果構造方法是privat

Java模式 多種實現方式

main 概念 ron system sta ava 實現 args == 一:通過靜態私有成員實現單例模式 (1):私有化構造函數 (2):new靜態實例屬性對象,加鎖。 單例類: package SinglePag; /* * 構造函數私有化,結合鎖+靜態的概念 實

Java 模式 總結整理

java 單例模式 分享總結常見的5種單例模式: 第一、單例模式的使用場景 A、Windows的任務管理器、回收站、文件系統如F盤,都是很典型的單例模式 ; B、項目中,讀取配置文件的類,一般也是單例模式,沒有必要每次讀取都重新new一個對象加載 C、數據庫的連接池也是

Java - 模式

單例 type youdao 只需要 需要 dao ava 模式 分享圖片 追加說明: ① SingletonHolder的final聲明和SINGLETON的final聲明可以去掉(不去也不會報錯)。 ② 以上實現方式是真單例(線程安全,絕對唯一),一般適用於需要保證絕

java 模式5種寫法

浪費 get public color ring 缺點 threads 構造函數 java 單例模式 學習整理 飽漢模式(懶漢模式) 1 // 飽漢 2 // UnThreadSafe 3 public class Singleton1 { 4 private

Java模式&static成員變量 區別

instance .net 技術分享 static成員 使用 過程 () http 一加 當需要共享的變量很多時,使用static變量占用內存的時間過長,在類的整個生命周期。 而對象只是存在於對象的整個生命周期。 //餓漢式 class Sing

Java-模式

ati 對象實例 get 方式 懶漢 pre sta log style 一、  單例模式:一種常用的軟件設計模式。通過單例模式可以保證系統中,應用該模式的類一個類只有一個實例。即一個類只有一個對象實例 二、  應用:線程池、緩存、日誌對象、對話框、打印機、顯卡的驅動程序對

Java 模式的七種寫法

context single ted 永遠 載器 枚舉 有關 urn pub 第一種(懶漢,線程不安全): public class Singleton { private static Singleton instance; private

Java模式幾種實現方式

開始 名稱 常量 就是 多線程開發 靜態代碼塊 浪費 ack 多線程同步 在平時的工作、學員的學習以及面試過程中,單例模式作為一種常用的設計模式,會經常被面試官問到,甚至筆試會要求學員現場默寫,下面將會就單例模式的實現思路和幾種常見的實現方式進行簡單的分享。

java模式(雙重檢查加鎖)的原因

csharp sta get 第一次 instance new 同步機制 原因 AR public class Singleton{ private static Singleton instance = null;//是否是final的不重要,因為最多只可能實

Java模式 volatile關鍵字作用的理解

初始 urn class .com 重新 on() 內存空間 sta 兩個   單例模式是程序設計中經常用到的,簡單便捷的設計模式,也是很多程序猿對設計模式入門的第一節課。其中最經典的一種寫法是: class Singleton { private volatil