單例模式--獨一無二的對象

1.單例模式的定義

單例模式(Singleton Pattern):用來建立獨一無二的,只能有一個實例的對象的入場券。html

在咱們進行開發的時候,有些對象咱們只須要一個,好比:配置文件,工具類,線程池、緩存、日誌對象等。若是創造出多個實例,就會致使許多問題,好比佔用資源過多,不一致的結果等。使用單例模式就能保證在程序中須要的實例只有一個。編程

單例模式的類型:懶漢模式、餓漢模式。緩存

 

2.單例模式的類圖

下面是單例模式的類圖:安全

咱們將建立一個單例對象類 - Singleton。單例對象(Singleton)類的構造函數是私有的,而且具備自身的靜態實例。多線程

Singleton類提供了一個靜態方法來獲取其靜態實例到外部世界。SingletonTest或示例類將使用Singleton類提供的靜態方法來獲取Singleton對象。yii

3.單例模式--餓漢式

餓漢式是在建立自身的靜態實例的時候就直接實例化單例對象,而後在提供的靜態方法中直接返回這個實例對象。函數

 1 /**
 2  * 單例模式(餓漢模式)
 3  * @author admin
 4  *
 5  */
 6 public class Singleton {
 7     //1.將構造方法私有化,不容許外部直接建立對象
 8     private Singleton(){
 9         }
10     
11     //2.建立類的惟一實例,使用private,static修飾
12     private static Singleton instance = new Singleton();
13     
14     //3.提供一個用於獲取實例的方法,使用public static 修飾
15     public static Singleton getInstance(){
16         return instance;
17     }
18 }    

4.單例模式-懶漢式

懶漢式在建立自身的靜態實例的時候不直接實例化單例對象,只有在外部調用獲取單例對象的靜態方法的時候纔會判斷單例對象是否已經存在,若是 不存在則建立一個並返回,若是已經存在則直接返回。工具

 1 /**
 2  * 單例模式(懶漢模式)
 3  * @author admin
 4  */
 5 public class Singleton2 {
 6     //1.將構造方法私有化,不容許外邊直接建立對象
 7     private Singleton2(){
 8     }
 9     //2.聲明類的惟一實例,使用private,static修飾,可是此處不實例化
10     private static Singleton2 instance;
11     //3.提供一個用於獲取實例的方法,使用public static修飾
12     public static Singleton2 getInstance(){
13         if (instance == null) {
14             instance = new Singleton2();
15         }
16         return instance;
17     }
18 }

 

 

5.測試

 1 /**
 2  * 單例模式的測試類
 3  * @author admin
 4  *
 5  */
 6 public class SingletonTest {
 7     public static void main(String[] args) {
 8         //餓漢模式
 9         Singleton s1 = Singleton.getInstance();
10         Singleton s2 = Singleton.getInstance();
11         
12         if (s1 == s2) {
13             System.out.println("s1和s2是同一個對象");
14         }else {
15             System.out.println("s1和s2不是同一個對象");
16         }
17         
18         //懶漢模式
19         Singleton2 s3 = Singleton2.getInstance();
20         Singleton2 s4 = Singleton2.getInstance();
21         if (s3 == s4) {
22             System.out.println("s3和s4是同一個對象");
23         }else {
24             System.out.println("s3和s4不是同一個對象");
25         }
26     }
27 }

 

測試結果:性能

從測試結果能夠看出來,獲取的對象是同一個對象,也就是說,返回的是單例對象。測試

6.餓漢式和懶漢式的區別

  1. 餓漢式是在單例類加載的時候就初始化單例對象,因此在加載類的時候速度比較慢,可是在運行時的速度比較快,同時是線程安全的(線程安全問題下面再詳細講解)。
  2. 懶漢式在單例類加載的時候只定義單例對象,不進行初始化,因此加載類的時候速度比較快,可是在運行時須要進行單例對象的初始化,因此在運行時速度比較慢,並且是線程不安全的。

7.單例模式在多線程中的問題

上面的代碼在普通的應用程序中沒有任何任何問題,可是在多線程中使用的時候就會發現,返回的單例對象並非惟一的,並且多個不一樣的單例對象,這就說明在多線程中,單例模式產生的單例對象並非"惟一"的。要解決這個問題,有3種辦法(其中兩種方法都是針對懶漢式的),這3種方法在解決多線程問題的同時也有本身的缺點:

  • 第一種方法:使用"急切"的建立實例,而不用延遲實例化的作法。若是程序老是建立並使用單例實例,或者在建立和運行時方面的負擔不太繁重,那就可使用急切(eagerly)建立此單例實例。其實這就是餓漢式單例模式。利用這個作飯,咱們依賴JVM在加載這個類是立刻建立此惟一的單例實例。JVM保證在任何線程訪問單例靜態變量以前,必定先建立此單例實例。
  • 第二種方法(此方法是針對懶漢式單例模式):把靜態類中的靜態方法(上面代碼中的getInstance())方法編程同步(synchronized)方法,多線程問題幾乎就能夠輕易的解決了。它的缺點是下降了性能,並且可能比這還更嚴重一些,經過單例模式的定義咱們知道,單例對象只有在該方法第一次執行時,才真正須要同步,在之後的調用中,該單例對象已經存在了,因此並不須要再次進行實例化。也就是說,當設置好instance這個變量後,就再也不須要同步這個方法了,以後的每次調用這個方法,同步都是一種累贅。並且,同步一個方法可能形成程序執行效率降低100倍,所以,若是在頻繁運行的地方調用該方法,那麼就得好好考慮這個問題了。固然,若是getInstance()方法的性能對應用程序不是很關鍵,那麼能夠忽略同步帶來的問題。
  • 第三種方法(此方法也是針對懶漢式單例模式):用"雙重檢查加鎖",在getInstance()方法中減小使用同步。利用雙重檢查加鎖(double-checked locking),首先檢查是否單例實例是否已經建立了,若是還沒有建立,"才"進行同步。這樣一來,只有第一次會同步,這正是咱們想要的。將上面懶漢式的代碼改爲以下:
     1 /**
     2  * 單例模式(懶漢模式)
     3  * @author admin
     4  */
     5 public class Singleton2 {
     6     //1.將構造方法私有化,不容許外邊直接建立對象
     7     private Singleton2(){
     8         
     9     }
    10     //2.聲明類的惟一實例,使用private,static修飾,可是此處不實例化
    11     private volatile static Singleton2 instance;
    12     //3.提供一個用於獲取實例的方法,使用public static修飾
    13     public static Singleton2 getInstance(){//檢查實例,若是不存在就進入同步區塊
    14         if (instance == null) {
    15             synchronized (Singleton2.class){//注意,只有第一次才完全執行這裏的代碼
    16                 if (instance == null) {//進入區塊後,再檢查一次,若是仍是null,才建立實例
    17                     instance = new Singleton2();
    18                 }
    19             }
    20         }
    21         return instance;
    22     }
    23 }

     volatile關鍵字確保:當單例變量被初始化成Singleton實例時,多個線程正確地處理單例變量。若是性能問題是關注的重點,那麼這個方法能夠大大地減小getInstance()的時間消耗。可是該方法也有其缺點,那就是雙重檢查加鎖不適用於1.4及更早版本的Java。在1.4及其更早的版本中,許多JVM對於volatile關鍵字的實現會致使雙重檢查加鎖的實效。若是不能使用Java5以上的版本,而必須使用舊版本,那麼該方法就沒法解決多線程的問題。

好了單例模式的敘述到此就結束了,若是有什麼講解的不正確的地方,歡迎你們多多指教!

文章部份內容引用自以下地址:http://www.yiibai.com/design_pattern/singleton_pattern.html

相關文章
相關標籤/搜索