Java設計模式學習筆記,一:單例模式

  開始學習Java的設計模式,由於作了不少年C語言,因此語言基礎的學習很快,可是面向過程向面向對象的編程思想的轉變仍是須要耗費不少的代碼量的。全部但願經過設計模式的學習,能更深刻的學習。java

  把學習過程當中的筆記,記錄下來,只記乾貨。編程

 

第一部分:單例模式的內容設計模式

  單例模式:類只能有一個實例。安全

  類的特色:一、私有構造器;二、內部構造實例對象;三、對外提供獲取惟一實例的public方法。ide

  常見的單例模式實現有五種形式:函數

    一、餓漢式。性能

    二、懶漢式。學習

    三、雙重檢查鎖式。測試

    四、靜態內部類式。spa

    五、枚舉式。

  如下分別介紹:

  1、餓漢式

    餓漢式單例特色:線程安全,不能延時加載,效率較高。

 1 public class SingletonDemoE {
 2     
 3     //內部構建惟一實例
 4     private static SingletonDemoE instance = new SingletonDemoE();
 5     
 6     //私有化構造器
 7     private SingletonDemoE(){
 8         
 9     }
10     
11     //公共靜態方法獲取惟一實例化對象
12     public static SingletonDemoE getInstance(){
13         return instance;
14     }
15     
16 }

  2、懶漢式

    懶漢式單例特色:線程安全(須synchronized作方法同步),能夠延時加載,效率較低。

 1 public class SingletonDemoL {
 2     
 3     //聲明實例對象
 4     private static SingletonDemoL instance;
 5     
 6     //私有化構造器
 7     private SingletonDemoL(){
 8         
 9     }
10     
11     //公共靜態方法獲取惟一實例化對象,方法同步
12     public static synchronized SingletonDemoL getInstance(){
13         if(instance == null){
14             //第一次實例化時構建
15             instance = new SingletonDemoL();
16         }
17         return instance;
18     }
19     
20 }    

 

  3、雙重檢查鎖式

    結合了餓漢式和懶漢式的優勢,但因爲JVM底層內部模型緣由,偶爾會出問題,因此不建議使用,本文不贅語。

  4、靜態內部類式

    靜態內部類式單例特色:線程安全(須synchronized作方法同步),能夠延時加載,效率較高。

 1 public class SingletonDemoJ {
 2     
 3     //靜態內部類
 4     private static class SingletonClassInstance {
 5         private static final SingletonDemoJ instance = new SingletonDemoJ();
 6     }
 7     
 8     //私有化構造器
 9     private SingletonDemoJ(){
10         
11     }
12     
13     //公共靜態方法獲取惟一實例化對象,方法同步
14     public static synchronized SingletonDemoJ getInstance(){
15         return SingletonClassInstance.instance;
16     }
17     
18 }

 

  5、枚舉式

    枚舉式單例特色:枚舉是自然的單例,線程安全,不可延時加載,效率較高

1 public enum SingletonDemoM {
2     //枚舉元素,自己就是單例模式
3     INSTANCE;
4     
5     //實現本身的操做
6     public void singletonOperation(){
7         
8     }
9 }

 

第二部分:單例模式的破解(擴展)

  單例模式的五種實現方式中,除枚舉式是自然的單例不可破解以外,其餘四種形式都可經過反射和反序列化的機制進行破解。

  以懶漢式單例爲例,首先分別看一下如何經過反射和反序列化的機制破解單例。

  定義一個常規的懶漢式單例:

 1 public class SingletonDemoAntiCrackL {
 2     private static SingletonDemoAntiCrackL instance;
 3     
 4     private SingletonDemoAntiCrackL(){
 5         
 6     }
 7 
 8     public static synchronized SingletonDemoAntiCrackL getInstance(){
 9         if(instance == null){
10             instance = new SingletonDemoAntiCrackL();
11         }
12         return instance;
13     }
14 }

  正常咱們建立多個單例的實例,都應該是同一個對象,以下測試Demo:

1 public class TestCrackDemo {
2     public static void main(String[] args) {
3         SingletonDemoAntiCrackL sL1 = SingletonDemoAntiCrackL.getInstance();
4         SingletonDemoAntiCrackL sL2 = SingletonDemoAntiCrackL.getInstance();
5         System.out.println("sL1 = " + sL1);
6         System.out.println("sL2 = " + sL2);
7     }
8 }

  運行返回:

sL1 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
sL2 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55

  利用反射機制破解單例,建立多個不一樣的實例:

 1 package com.corey.singleton;
 2 
 3 import java.lang.reflect.Constructor;
 4 
 5 public class TestCrackDemo {
 6     public static void main(String[] args) {
 7         SingletonDemoAntiCrackL sL1 = SingletonDemoAntiCrackL.getInstance();
 8         SingletonDemoAntiCrackL sL2 = SingletonDemoAntiCrackL.getInstance();
 9         System.out.println("sL1 = " + sL1);
10         System.out.println("sL2 = " + sL2);
11         
12         //利用反射機制破解單例
13         try {
14             Class<SingletonDemoAntiCrackL> clazz = (Class<SingletonDemoAntiCrackL>)Class.forName("com.corey.singleton.SingletonDemoAntiCrackL");
15             Constructor<SingletonDemoAntiCrackL> c = clazz.getDeclaredConstructor(null);
16             c.setAccessible(true); //跳過權限檢查,能夠訪問私有屬性和方法
17             SingletonDemoAntiCrackL sL3 = c.newInstance(null);    //構建實例
18             SingletonDemoAntiCrackL sL4 = c.newInstance(null);
19             System.out.println("sL3 = " + sL3);
20             System.out.println("sL4 = " + sL4);
21             
22         } catch (Exception e) {
23             e.printStackTrace();
24         }
25     }
26 }

  運行返回:

sL1 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
sL2 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
sL3 = com.corey.singleton.SingletonDemoAntiCrackL@15db9742
sL4 = com.corey.singleton.SingletonDemoAntiCrackL@6d06d69c

  可見,經過反射構建的sL3和sL4兩個對象,都是不一樣的實例,破解了單例模式只能有一個實例的要求。

  那麼,修改單例的構造函數,能夠應對反射機制的破解,代碼以下:

 1 public class SingletonDemoAntiCrackL {
 2     private static SingletonDemoAntiCrackL instance;
 3     
 4     private SingletonDemoAntiCrackL(){
 5         //私有構造器,增長實例檢查,若已建立實例,則拋出異常
 6         if(instance != null){            
 7             throw new RuntimeException();
 8         }
 9     }
10 
11     public static synchronized SingletonDemoAntiCrackL getInstance(){
12         if(instance == null){
13             instance = new SingletonDemoAntiCrackL();
14         }
15         return instance;
16     }
17 }

  此時,在運行TestCrackDemo時,會拋出java.lang.reflect.InvocationTargetException異常,避免了經過反射機制建立多個實例的問題。

  接下來,看下經過反序列化機制破解單例。

  當單例的類實現了Serializable接口時,就能夠經過反序列化機制破解,以下單例:

 1 public class SingletonDemoAntiCrackL implements Serializable{
 2     private static SingletonDemoAntiCrackL instance;
 3     
 4     private SingletonDemoAntiCrackL(){
 5         //私有構造器,增長實例檢查,若已建立實例,則拋出異常
 6         if(instance != null){            
 7             throw new RuntimeException();
 8         }
 9     }
10 
11     public static synchronized SingletonDemoAntiCrackL getInstance(){
12         if(instance == null){
13             instance = new SingletonDemoAntiCrackL();
14         }
15         return instance;
16     }
17 }

  反序列化破解測試Demo:

 1 package com.corey.singleton;
 2 
 3 import java.io.FileInputStream;
 4 import java.io.FileNotFoundException;
 5 import java.io.FileOutputStream;
 6 import java.io.ObjectInputStream;
 7 import java.io.ObjectOutputStream;
 8 import java.lang.reflect.Constructor;
 9 
10 public class TestCrackDemo {
11     public static void main(String[] args) {
12         SingletonDemoAntiCrackL sL1 = SingletonDemoAntiCrackL.getInstance();
13         SingletonDemoAntiCrackL sL2 = SingletonDemoAntiCrackL.getInstance();
14         System.out.println("sL1 = " + sL1);
15         System.out.println("sL2 = " + sL2);
16 
17         //經過反序列化機制破解單例
18         try {
19             //序列化,將對象存入文件
20             FileOutputStream fos = new FileOutputStream("f:/fos.txt");
21             ObjectOutputStream oos = new ObjectOutputStream(fos);
22             oos.writeObject(sL1);
23             oos.close();
24             fos.close();
25             //反序列化,從文件中讀出對象
26             ObjectInputStream ois = new ObjectInputStream(new FileInputStream("f:/fos.txt"));
27             SingletonDemoAntiCrackL sL5 = (SingletonDemoAntiCrackL)ois.readObject();
28             System.out.println("sL5 = " + sL5);
29         } catch (Exception e) {
30             e.printStackTrace();
31         }
32     }
33 }

  運行的結果:

sL1 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
sL2 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
sL5 = com.corey.singleton.SingletonDemoAntiCrackL@33909752

  可見,反序列化出來的實例對象,是不一樣的對象,即單例已被破解。

  解決版本,單例類中重寫readResolve方法,能夠應對反射機制的破解,代碼以下:

 1 package com.corey.singleton;
 2 
 3 import java.io.ObjectStreamException;
 4 import java.io.Serializable;
 5 
 6 /**
 7  * 單例模式,懶漢式
 8  * 線程安全,能延時加載,效率相對較低
 9  * @author Corey
10  *
11  */
12 public class SingletonDemoAntiCrackL implements Serializable{
13     private static SingletonDemoAntiCrackL instance;
14     
15     private SingletonDemoAntiCrackL(){
16         //私有構造器,增長實例檢查,若已建立實例,則拋出異常
17         if(instance != null){            
18             throw new RuntimeException();
19         }
20     }
21 
22     public static synchronized SingletonDemoAntiCrackL getInstance(){
23         if(instance == null){
24             instance = new SingletonDemoAntiCrackL();
25         }
26         return instance;
27     }
28     
29     private Object readResolve() throws ObjectStreamException{
30         //反序列化時,直接返回對象
31         return instance;
32     }
33 }

  修改後,再次運行TestCrackDemo,能夠看到反序列化後,構建的仍然是同一個對象。

sL1 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
sL2 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
sL5 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55

 

第三部分:單例模式各個實現方式的效率

  採用以下代碼,測試:

 1 package com.corey.singleton;
 2 
 3 import java.util.concurrent.CountDownLatch;
 4 
 5 /**
 6  * 測試單例模式的效率
 7  * @author Corey
 8  *
 9  */
10 public class TestEfficiencyDemo {
11     public static void main(String[] args) throws Exception {
12         
13         int threadNum = 10;
14         long start = System.currentTimeMillis();
15         
16         final CountDownLatch cdl = new CountDownLatch(threadNum);
17         
18         //建立10個線程
19         for(int k=0; k<threadNum; k++){
20             new Thread(new Runnable() {
21                 
22                 @Override
23                 public void run() {
24                     //每一個線程構建100萬個實例對象
25                     for(int i=0; i<1000000; i++){
26                         Object o = SingletonDemoE.getInstance();
27                     }
28                     //每一個線程運行完畢,線程計數器減一
29                     cdl.countDown();
30                 }
31             }).start();
32         }
33         
34         cdl.await();//main線程阻塞,直到現場計數器爲0,才繼續執行。
35         
36         long end = System.currentTimeMillis();
37         System.out.println("餓漢式總耗時:" + (end - start));
38     }
39 }

  運行結果:

餓漢式總耗時:17

  以此類推,測試各個實現方式的單例的效率。注意,此處根據電腦性能以及電腦的運行狀況不一樣,結果都是不同的,甚至同一實現方式,屢次運行的結果也不同。

  我這裏的測試結果以下:

餓漢式總耗時:17
懶漢式總耗時:171
靜態內部類式總耗時:165
枚舉式總耗時:11

 

  以上就是設計模式中的單例模式!

相關文章
相關標籤/搜索