程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 單例模式,模式

單例模式,模式

編輯:JAVA綜合教程

單例模式,模式


1.優缺點

  單利模式就是在一個jvm中只能存在一個實例(不考慮反射)這樣設計主要有兩方面好處:

    1.從jvm來說,對於頻繁使用的對象,可以減去創建的時間(這對於重量級的對象,是非常客觀的開銷),由於new 對象的操作減少,對系統內存的使用頻率降低,將會減輕GC壓力,縮短GC停頓時間(摘自 java程序性能優化 --葛一鳴)。

    2.從設計來講,某些實例一個系統中本應只存在一個(邏輯上),並且只對同一對象操作,能有效的保證一致性(並發時可相應處理)。

  同時也存在一些需要注意的問題:   

    1、由於單利模式中沒有抽象層,不利於擴展,所以很多責任都是自己扛,可能會導致單例類的職責過重,在一定程度上違背了“單一職責原則”。

    2.如這個實例創建過程很慢而且不一定會用到,可能需要延遲加載。

    3、濫用單例將帶來一些負面問題,如為了節省資源將數據庫連接池對象設計為的單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;如果實例化的對象長時間不被利用,系統會認為是垃圾而被回收,這將導致對象狀態的丟失。

2.實現方式

  實現單例的根本是私有化構造器(在類內部創建對象),然後根據不同的場景設計獲取實例的方法,下面是幾種常見的實現方式。

  1.餓漢式: 這種方式在類加載時就完成了初始化,所以類加載較慢,但獲取對象的速度快。 基於類加載機制實現可避免多線程的同步問題,但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化instance顯然沒有達到延遲加載的效果。  

1 public class Singleton1 {//惡漢式
2     private Singleton1(){}
3     private static Singleton1 singleton = new Singleton1();
4     public static Singleton1 getInstance(){
5         return  singleton;
6     }
7 }

  2.懶漢式:可實現延遲加載,但是多線程下存在致命問題。



 1 public class Singleton2 {//懶漢式
 2     private Singleton2(){};
 3     private static Singleton2 singleton;
 4     public static Singleton2 getInstance(){
 5         if(singleton == null){
 6             singleton = new Singleton2();
 7         }
 8         return  singleton;
 9     }
10 }

  3.雙重檢查模式:改進的懶漢式。為解決線程同步問題,最簡單的方法是對getInstance方法整體加關鍵字synchronized,但是這種實現方式效率會至少低2個數量級。其中一種不錯的改進方式是雙重檢查模式(DCL),這種寫法在getSingleton方法中對singleton進行了兩次判空,第一次是為了不必要的同步,第二次是在singleton等於null的情況下才創建實例。在這裡用到了volatile關鍵字,在這裡使用volatile會或多或少的影響性能,但考慮到程序的正確性,犧牲這點性能還是值得的。DCL優點是資源利用率高,第一次執行getInstance時單例對象才被實例化,效率高。缺點是第一次加載時反應稍慢一些,在高並發環境下也有一定的缺陷(DCL失效),雖然發生的概率很小。

 1 public class Singleton3 {//雙重檢查模式(DCL)
 2     private Singleton3(){};
 3     private static volatile Singleton3 singleton;
 4     public static Singleton3 getInstance(){
 5         if(singleton == null){
 6             synchronized(Singleton3.class){
 7                 if(singleton == null){
 8                     singleton = new Singleton3();
 9                 }
10             }
11         }
12         return  singleton;
13     }
14 }

  4.靜態內部類:既可以實現延遲加載,又不會有線程問題(推薦

1 public class Singleton4 {//靜態內部類模式(DCL)
2     private Singleton4(){};
3     static class ClassHolder{
4         private static Singleton4 singleton = new Singleton4();
5     }
6     public static Singleton4 getInstance(){
7         return  ClassHolder.singleton;
8     }
9 }

  5.枚舉:這種方式是Effective java作者Josh Bloch提倡的方式,它不僅能避免多線程的同步問題,而且還能防止反序列化重新創建對象的問題。聽起來很給力,不過工作中基本見過,有機會試試。

1 public enum Singleton5 {//枚舉模式
2     INSTANCE;
3     public void whateverMethod(){
4         //同枚舉類使用(它本來就是枚舉!),不需要獲取實例的方法。
5     }
6 }

  6.利用容器保證單例:在不考慮容器本身對並發的處理的情況下,這種方式能有效管理多種類型的單例,並且在使用時可以通過統一的接口進行獲取操作,降低了用戶的使用成本,也對用戶隱藏了具體實現,降低了耦合度。

 1 //利用容器實現單例
 2 //最好將你要裝載的單例對象構造函數私有化,這樣可以避免很多問題
 3 // 如果你能保證每次都是從這個類加載器獲取對象,構造函數是否私有化毫不相干
 4 public class SingletonManager {
 5     private static Map<String, Object> objMap = new HashMap<String,Object>();
 6     public static void registerService(String key, Object instance) {
 7         if (!objMap.containsKey(key) ) {
 8             objMap.put(key, instance) ;
 9         }
10 }
11     public static Object ObjectGetService(String key) {
12         return objMap.get(key) ;
13     }
14 }

 3.破壞單例的情況

  1.反射:就當他不存在吧。

  2.多個類加載器:例如一些servlet容器對每個servlet使用完全不同的類裝載器,這樣的話如果有兩個servlet訪問一個單例類,它們就都會有各自的實例。解決方法就是想辦法使用同一個類加載器(廢話)。

  3.序列化復原:當你序列化復原一個單例對象時候,就會出現多個單例對象。如這樣:

 1 public class Test {
 2     public static void main(String[] args) throws IOException, ClassNotFoundException {
 3         Singleton1 singleton = Singleton1.getInstance();
 4         //先將單例對象序列化到文件
 5         FileOutputStream outputStream = new FileOutputStream("E:Singleton.txt");
 6         ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
 7         objectOutputStream.writeObject(singleton);
 8         objectOutputStream.flush();
 9         objectOutputStream.close();
10         //從文件讀取對象
11         FileInputStream inputStream = new FileInputStream("E:Singleton.txt");
12         ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
13         Singleton1 newSingleton = (Singleton1) objectInputStream.readObject();
14 
15         //test
16         System.out.println(singleton == newSingleton); // false
17         System.out.println(singleton == Singleton1.getInstance());// true
18     }
19 
20 }

  有效的方法是在單例類中加入readResolve方法(序列化操作提供了一個很特別的鉤子(hook)類中具有一個私有的被實例化的方法readresolve(),這個方法可以確保類的開發人員在序列化將會返回怎樣的object上具有發言權)。例如:

 1 public class Singleton1 implements Serializable{//惡漢式
 2     private Singleton1(){}
 3     private static Singleton1 singleton = new Singleton1();
 4     public static Singleton1 getInstance(){
 5         return  singleton;
 6     }
 7     private Object readResolve(){
 8         return singleton;
 9     }
10 }

  在運行上面的測試就會的到兩個true。

4.感謝:很多內容是從網上檢索整理得,感謝感謝!!

 

 

  

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved