1. 程式人生 > >Java設計模式-單例模式

Java設計模式-單例模式

影響 沒有 環境 成員 == 比較 .net 非線程安全 align

轉自:http://blog.csdn.net/jason0539/article/details/23297037

  Java中單例模式是一種常見的設計模式,單例模式的寫法有多種,這裏主要只介紹:懶漢式單例、餓漢式單例

  單例模式確保某個類只有一個實例,而且自行實例化並向整個系統提供這個實例

  選擇單例模式就是為了避免不一致狀態,同時也可減少資源消耗

一、懶漢式單例

 1 //懶漢式單例類.在第一次調用的時候實例化自己   
 2 public class Singleton {  
 3     private Singleton() {}  
 4     private static Singleton single=null
; 5 //靜態工廠方法 6 public static Singleton getInstance() { 7 if (single == null) { 8 single = new Singleton(); 9 } 10 return single; 11 } 12 }

  Singleton通過將構造方法限定為private避免了類在外部被實例化,在同一個虛擬機範圍內,Singleton的唯一實例只能通過getInstance()方法訪問。

  但是以上懶漢式單例的實現沒有考慮線程安全問題,它是線程不安全的,並發環境下很可能出現多個Singleton實例,要實現線程安全,有以下三種方式,都是對getInstance這個方法改造,保證了懶漢式單例的線程安全

  1、在getInstance方法上加同步

1 public static synchronized Singleton getInstance() {  
2          if (single == null) {    
3              single = new Singleton();  
4          }    
5         return
single; 6 }

  2、雙重檢查鎖定

 1 public static Singleton getInstance() {  
 2         if (singleton == null) {    
 3             synchronized (Singleton.class) {    
 4                if (singleton == null) {    
 5                   singleton = new Singleton();   
 6                }    
 7             }    
 8         }    
 9         return singleton;   
10 } 

  3、靜態內部類

  

1 public class Singleton {    
2     private static class LazyHolder {    
3        private static final Singleton INSTANCE = new Singleton();    
4     }    
5     private Singleton (){}    
6     public static final Singleton getInstance() {    
7        return LazyHolder.INSTANCE;    
8     }    
9 }    

  靜態內部類的方式比1、2種都要好一些,既實現了線程安全,有避免了同步帶來的性能影響

 1 package com.singleton;
 2 
 3 //懶漢式單例類,在第一次調用的時候實例化自己
 4 //但是懶漢式單例的實現沒有考慮線程安全問題,它是線程不安全的,並發環境下很可能出現多個Singleton實例
 5 //為什麽會出現線程安全問題呢?
 6 //當多個線程去調用 Class.getInstance 時,在線程1自己的內存空間中,是沒有實例的,所以會新創建一個,線程2又去調用instance,又創建一個
 7 
 8 //可以有三種改進方法:1.在getInstance方法上加同步  2.雙重檢查鎖定  3.靜態內部類(這種比較好)
 9 public class SingletonLazy {
10     private SingletonLazy() {
11     }
12 
13     // 在類被初始化的時候,不會為這個成員變量賦值(懶),必須要等到調用 getInstance 才會去創建實例
14     /*private static SingletonLazy singleton = null;  (這裏不能加final,初始為null,加了final 之後,指向不能改變,就一直是null了)
15 
16     // 靜態工廠方法
17     public static SingletonLazy getInstance() {
18         if (singleton == null) {
19             singleton = new SingletonLazy();
20         }
21         return singleton;
22     }*/
23 
24     //靜態內部類的方式,在SingletonLazy類被初始化的時候,沒有成員變量可以去初始化
25     //當調用 getInstance 的時候,通過類加載器的方式 加載LazyHolder,同時加載LazyHolder 的單例實例
26     //因為類加載器的方式 是線程安全的,所以 這個也是線程安全的
27     private static class LazyHolder {
28         private static final SingletonLazy INSTANCE = new SingletonLazy();  //(加上final,創建後不會被修改)
29     }
30 
31     public static final SingletonLazy getInstance() {
32         return LazyHolder.INSTANCE;
33     }
34 }
35     

二、餓漢式單例

 1 //餓漢式單例,在類初始化的時候,已經自行實例化
 2 //在真正調用這個類的時候,才會完成這個類的初始化,為靜態成員變量賦初始值,
 3 //保證getInstance 的時候,單例已經存在了
 4 //為什麽這樣是線程安全的呢?
 5 //猜想:類的加載初始化只會加載一次,SingletonHungry也只會被創建一次,(加上final,創建後不會被修改) 當多個線程去調用Class.getInstance方法時,getInstance做的不是創建,而是把我早就創建好的直接給你,所以拿到的是同一個實例(一個引用變量)
 6 public class SingletonHungry {
 7     private static final SingletonHungry singleton = new SingletonHungry();
 8     
 9     private SingletonHungry(){};
10     
11     public static SingletonHungry getInstance() {
12         return singleton;
13     }
14 }

  餓漢式在類創建的同時就已經創建好一個靜態的對象供系統使用,以後不再改變,所以天生是線程安全的。

三、餓漢式和懶漢式的區別:

  餓漢就是類一旦加載,就把單例初始化完成,保證getInstance的時候,單例是已經存在的了,

  而懶漢比較懶,只有當調用getInstance的時候,才會去初始化這個單例

  另外從以下兩點再區分以下這兩種方式:

  1、線程安全:

  餓漢式天生就是線程安全的,可以直接用於多線程而不會出現問題,

  懶漢式本身是非線程安全的,為了實現線程安全有幾種寫法,分別是上面的1、2、3,這三種實現在資源加載和性能方面有些區別。



  2、資源加載和性能:

  餓漢式在類創建的同時就實例化一個靜態對象出來,不管之後會不會使用這個單例,都會占據一定的內存,但是相應的,在第一次調用時速度也會更快,因為其資源已經初始化完成,

  而懶漢式顧名思義,會延遲加載,在第一次使用該單例的時候才會實例化對象出來,第一次調用時要做初始化,如果要做的工作比較多,性能上會有些延遲,之後就和餓漢式一樣了。

  至於1、2、3這三種實現又有些區別,

    第1種,在方法調用上加了同步,雖然線程安全了,但是每次都要同步,會影響性能,畢竟99%的情況下是不需要同步的,

    第2種,在getInstance中做了兩次null檢查,確保了只有第一次調用單例的時候才會做同步,這樣也是線程安全的,同時避免了每次都同步的性能損耗

    第3種,利用了classloader的機制來保證初始化instance時只有一個線程,所以也是線程安全的,同時沒有性能損耗,所以一般我傾向於使用這一種

Java設計模式-單例模式