軟件設計模式之單例模式(JAVA)


什么是單例模式?

單例模式是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱為單例類的特殊類。通過單例模式可以保證系統中一個類只有一個實例而且該實例易於外界訪問,從而方便對實例個數的控制並節約系統資源。如果希望在系統中某個類的對象只能存在一個,單例模式是最好的解決方案。

 

單例模式的特點:

1、單例類有且只能有一個實例。

2、單例類需要自己創建一個自己的實例。

3、單例類需要為其他類提供這個實例。

 

哪些地方經常用到單例?

在計算機系統中,配置文件,線程池,緩存,日志對象,打印機等經常用到單例模式

所謂“一山不容二虎”,如果出現二虎很容易會出問題,比如配置文件,它終究只是一個文件,如果同時有好幾個實例訪問它並執行修改操作,那么這時候就會引發出一系列的問題 

單例相對於多實例對象也更節約系統資源

 

單例模式常用的有幾種模式?

一般來講單例模式有三種,分別是:懶漢式,餓漢式,登記式。

 

下面直接上代碼說明吧

①一個類之所以能夠創建出實例是因為有構造方法的存在,只要我們把構造方法的訪問修飾符改成私有(private),外界就不能通過new來創建該類的實例。

②在單例類中自身new出一個對象,因為要被外界訪問,我們可以把它靜態化(static),以便外界訪問(類型.對象)

③有時候我們需要控制這個對象,也處於安全起見,我們可以把繼續私有化(private),然后提供一個getter方法以便外界訪問

SimpletonDemo1.java(單例類)

 1 package com.lcw.simpleton;
 2 
 3 public class SimpletonDemo1 {
 4     //將構造方法私有化,阻止外界直接創建對象
 5     private SimpletonDemo1() {
 6     }
 7     //提供static以便外界訪問
 8     private static SimpletonDemo1 instance = new SimpletonDemo1();
 9 
10     //提供getter方法以便外界訪問私有化對象,static SimpleDemo1返回類型
11     public static SimpletonDemo1 getInstance() {
12         return instance;
13     }
14 }

SimpletonTest.java(測試類)

 1 package com.lcw.simpleton;
 2 
 3 public class SimpletonTest {
 4     public static void main(String[] args) {
 5         SimpletonDemo1 s1=SimpletonDemo1.getInstance();
 6         SimpletonDemo1 s2=SimpletonDemo1.getInstance();
 7         
 8         if(s1==s2){//檢測對象的內存地址是否一致
 9             System.out.println("s1和s2是同個對象");
10         }else{
11             System.out.println("s1和s2不是同個對象");
12         }
13         
14     }
15 }

效果如下:

 上面所說的就是單例模式里餓漢模式,為什么叫餓漢模式呢?

由於這個實例是被static所修飾,被static修飾的成員屬於類所有,當類加載的時候,這個成員就被加載了,也就是說不管外界是否調用這個類,它都已經被加載了。

看起來好像是餓漢,不管三七二十一,先吃了你再說。

 

下面再來看下單例模式中的懶漢模式

就字面上的意思,其實已經很明白了“懶漢模式”,顧名思義不同於餓漢模式,既然餓漢模式是不管三七二十一先吃了再說,那么懶漢模式當然就沒那么勤快了,應該是被我們調用后的時候才去實例化對象。

它們的寫法很類似,只不過是在用private static聲明對象的時候不直接new對象,而是在gette方法里再去實例化對象

然后判斷下這個對象是否為null,如果為null則實例化一個對象,如果不會空則直接返回對象。

下面看下具體代碼

SimpletonDemo2.java(單例類)

 1 package com.lcw.simpleton;
 2 
 3 public class SimpletonDemo2 {
 4     //將構造方法私有化,阻止外界直接創建對象
 5     private SimpletonDemo2() {
 6     }
 7     //提供static以便外界訪問
 8     private static SimpletonDemo2 instance;
 9 
10     //提供getter方法以便外界訪問私有化對象,static SimpleDemo1返回類型
11     public static SimpletonDemo2 getInstance() {
12         if(instance==null){
13             return instance=new SimpletonDemo2();
14         }else{
15             return instance;
16         }
17     }
18 }

 

SimpletonTest.java(測試類)

 1 package com.lcw.simpleton;
 2 
 3 public class SimpletonTest {
 4     public static void main(String[] args) {
 5         SimpletonDemo1 s1=SimpletonDemo1.getInstance();
 6         SimpletonDemo1 s2=SimpletonDemo1.getInstance();
 7         
 8         if(s1==s2){//檢測對象的內存地址是否一致
 9             System.out.println("s1和s2是同個對象");
10         }else{
11             System.out.println("s1和s2不是同個對象");
12         }
13         
14         
15         SimpletonDemo2 s3=SimpletonDemo2.getInstance();
16         SimpletonDemo2 s4=SimpletonDemo2.getInstance();
17         
18         if(s3==s4){//檢測對象的內存地址是否一致
19             System.out.println("s3和s4是同個對象");
20         }else{
21             System.out.println("s3和s4不是同個對象");
22         }
23         
24     }
25 }

效果如下:

 

總結下兩種模式的區別:

1、餓漢式,在加載類的時候比較慢,由於它還要去實例化一個對象並造成內存資源的浪費,但在運行調用中的速度會比較快。

2、懶漢式,在加載類的時候比較快,由於在加載類的時候不需要去實例化對象,但在運行調用時的速度比較慢,由於還要去做判斷。

還有一點很重要的是,餓漢模式是屬於線程安全,而懶漢模式屬於線程不安全,在高並發時會出現問題。

 

那有沒有更好的方式呢?答案肯定是有的,我們可以結合了餓漢式和懶漢式各自的優點,不在加載類的時候實例化不必要的對象,又同時具備了線程安全

說直白的點就是利用內部類去靜態實例化這個對象,由於內部類需要在外部類被調用的時候才會去加載,也就是說不會在沒必要的時候去加載對象從而導致系統資源的浪費,同時我們在內部類中對這個對象實行靜態實例化也就避免了線程安全這個問題。

 1 package com.lcw.simpleton;
 2 
 3 public class SimpletonDemo {
 4     // 將構造方法私有化,阻止外界直接創建對象
 5     private SimpletonDemo() {
 6     }
 7 
 8     // 定義一個內部類用來實例化對象
 9     private static class Inner_SimpletonDemo {
10         private static SimpletonDemo instance = new SimpletonDemo();
11     }
12 
13     // 提供getter方法以便外界訪問私有化對象,static SimpleDemo1返回類型
14     public static SimpletonDemo getInstance() {
15         return Inner_SimpletonDemo.instance;
16     }
17 }

 

再來看下最后一種實現方式,登記式

由於懶漢式和餓漢式都把構造方法私有化了,所以它不能被繼承,登記式可以解決這個問題

登記式單例模式  類似於Spring里面的用法,將類名注冊並放到Map集合里,下次要用的時候直接取

登記式實際對一組單例模式進行的維護,主要是在數量上的擴展,通過map我們把單例存進去,這樣在調用時,先判斷該單例是否已經創建,是的話直接返回,不是的話創建一個登記到map中,再返回。對於數量又分為固定數量和不固定數量的。下面采用的是不固定數量的方式,在getInstance方法中加上參數(string name),然后通過子類繼承,重寫這個方法將name傳進去。

SimpletonDemo3.java(單例類)

 1 package com.lcw.simpleton;
 2 
 3 import java.util.HashMap;
 4 import java.util.Map;
 5 
 6 public class SimpletonDemo3 {
 7     // 私有化構造器,保護鈎子
 8     private SimpletonDemo3() {
 9     }
10 
11     private static Map<String, SimpletonDemo3> map = new HashMap<String, SimpletonDemo3>();
12     //靜態代碼塊
13     static {
14         SimpletonDemo3 instance = new SimpletonDemo3();
15         map.put(instance.getClass().getName(), instance);
16     }
17 
18     // 參數name為類名
19     public static SimpletonDemo3 getInstance(String name) {
20         if (name == null) {
21             name = "com.lcw.simpleton.SimpletonDemo3";
22         }
23         if (!map.containsKey(name)) {
24             try {
25                 map.put(name, (SimpletonDemo3) Class.forName(name)
26                         .newInstance());
27             } catch (InstantiationException e) {
28                 e.printStackTrace();
29             } catch (IllegalAccessException e) {
30                 e.printStackTrace();
31             } catch (ClassNotFoundException e) {
32                 e.printStackTrace();
33             }
34         }
35         return map.get(name);
36     }
37 
38 }

SimpletonTest.java(測試類)

 1 package com.lcw.simpleton;
 2 
 3 public class SimpletonTest {
 4     public static void main(String[] args) {
 5         SimpletonDemo1 s1 = SimpletonDemo1.getInstance();
 6         SimpletonDemo1 s2 = SimpletonDemo1.getInstance();
 7 
 8         if (s1 == s2) {// 檢測對象的內存地址是否一致
 9             System.out.println("s1和s2是同個對象");
10         } else {
11             System.out.println("s1和s2不是同個對象");
12         }
13 
14         SimpletonDemo2 s3 = SimpletonDemo2.getInstance();
15         SimpletonDemo2 s4 = SimpletonDemo2.getInstance();
16 
17         if (s3 == s4) {// 檢測對象的內存地址是否一致
18             System.out.println("s3和s4是同個對象");
19         } else {
20             System.out.println("s3和s4不是同個對象");
21         }
22 
23         SimpletonDemo3 s5 = SimpletonDemo3.getInstance("com.lcw.simpleton.SimpletonDemo3");
24         SimpletonDemo3 s6 = SimpletonDemo3.getInstance("com.lcw.simpleton.SimpletonDemo3");
25         if (s5 == s6) {// 檢測對象的內存地址是否一致
26             System.out.println("s5和s6是同個對象");
27         } else {
28             System.out.println("s5和s6不是同個對象");
29         }
30     }
31 }

效果圖:

 

 

 

作者:Balla_兔子
出處:http://www.cnblogs.com/lichenwei/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。
正在看本人博客的這位童鞋,我看你氣度不凡,談吐間隱隱有王者之氣,日后必有一番作為!旁邊有“推薦”二字,你就順手把它點了吧,相得准,我分文不收;相不准,你也好回來找我!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM