1. 程式人生 > >01設計模式——單例模式

01設計模式——單例模式

dsi 同步塊 .com 調優 博客 true 指令 反射 run

前言:以下總結來自龍哥---左瀟龍博客。

總結的很到位,附上博客鏈接:http://www.cnblogs.com/zuoxiaolong/p/pattern2.html

目的:盡可能的節約內存空間,減少無謂的GC消耗,並且使應用可以正常運作。

如何判斷單例:這些類,在應用中如果有兩個或者兩個以上的實例會引起錯誤,又或者我換句話說,就是這些類,在整個應用中,同一時刻,有且只能有一種狀態。

 1 /**
 2  * 簡單單例模式 
 3  * 目的,是為了盡可能的節約內存空間,減少無謂的GC消耗,並且使應用可以正常運作。
 4  * 一般一個類能否做成單例,最容易區別的地方就在於,這些類,在應用中如果有兩個或者兩個以上的實例會引起錯誤,又或者我換句話說,就是這些類,在整個應用中,同一時刻,有且只能有一種狀態。
 5  * 1.靜態實例,帶有static關鍵字的屬性在每一個類中都是唯一的。
 6  * 2.限制客戶端隨意創造實例,即私有化構造方法,此為保證單例的最重要的一步。
 7  * 3.給一個公共的獲取實例的靜態方法,註意,是靜態的方法,因為這個方法是在我們未獲取到實例的時候就要提供給客戶端調用的,所以如果是非靜態的話,那就變成一個矛盾體了,因為非靜態的方法必須要擁有實例才可以調用。
 8  * 4.判斷只有持有的靜態實例為null時才調用構造方法創造一個實例,否則就直接返回。
 9  */
10 public class Singleton {
11 
12     // 一個靜態的實例
13     private static Singleton singleton;
14 
15     // 私有化構造函數
16     private Singleton() {
17         super();
18     }
19 
20     // 給出一個公共的靜態方法返回一個單一實例
21     public static Singleton getInstance() {
22         if (singleton == null) {
23             singleton = new Singleton();
24         }
25         return singleton;
26     }
27 
28     // 驗證單例
29     public static void main(String[] args) {
30         Singleton instance1 = Singleton.getInstance();
31         Singleton instance2 = Singleton.getInstance();
32         System.out.println(instance1);
33         System.out.println(instance2);
34     }
35 }
 1 import java.util.Collections;
 2 import java.util.HashSet;
 3 import java.util.Set;
 4 import java.util.concurrent.ExecutorService;
 5 import java.util.concurrent.Executors;
 6 import java.util.concurrent.TimeUnit;
 7 
 8 /** 
 9  * 驗證Singleton線程不安全
10  */
11 public class TestSingleton {
12 
13     private volatile boolean lock;
14 
15     public boolean isLock() {
16         return lock;
17     }
18 
19     public void setLock(boolean lock) {
20         this.lock = lock;
21     }
22 
23     /**
24      * 當並發訪問的時候,第一個調用getInstance方法的線程A,在判斷完singleton是null的時候,線程A就進入了if塊準備創造實例,
25      * 但是同時另外一個線程B在線程A還未創造出實例之前,就又進行了singleton是否為null的判斷,這時singleton依然為null,
26      * 所以線程B也會進入if塊去創造實例,這時問題就出來了,有兩個線程都進入了if塊去創造實例,結果就造成單例模式並非單例。
27      * */
28     public static void main(String[] args) throws InterruptedException {
29         final Set<String> instanceSet = Collections.synchronizedSet(new HashSet<String>());
30         final TestSingleton lock = new TestSingleton();
31         lock.setLock(true);
32         ExecutorService executorService = Executors.newCachedThreadPool();
33         for (int i = 0; i < 100; i++) {
34             executorService.execute(new Runnable() {
35 
36                 public void run() {
37                     while (true) {
38                         if (!lock.isLock()) {
39                             Singleton singleton = Singleton.getInstance();
40                             instanceSet.add(singleton.toString());
41                             break;
42                         }
43                     }
44                 }
45             });
46         }
47         // main線程睡眠了兩次
48         Thread.sleep(5000);// 第一次是為了給足夠的時間讓100個線程全部開啟,
49         lock.setLock(false);
50         Thread.sleep(5000);// 第二個是將鎖打開以後,保證所有的線程都已經調用了getInstance方法。
51         System.out.println("------並發情況下我們取到的實例------");
52         for (String instance : instanceSet) {
53             System.out.println(instance);
54         }
55         executorService.shutdown();
56         executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
57         System.out.println("程序運行結束!!!");
58     }
59 }
 1 /**
 2  * 就是將整個獲取實例的方法同步,
 3  * 這樣在一個線程訪問這個方法時,其它所有的線程都要處於掛起等待狀態,倒是避免了剛才同步訪問創造出多個實例的危險,
 4  * 這樣的設計實在是糟糕透了,這樣會造成很多無謂的等待。
 5  */
 6 public class BadSynchronizedSingleton {
 7 
 8     // 一個靜態的實例
 9     private static BadSynchronizedSingleton singleton;
10 
11     // 私有化構造函數
12     private BadSynchronizedSingleton() {
13         super();
14     }
15 
16     // 給出一個公共的靜態方法返回一個單一實例
17     public static BadSynchronizedSingleton getInstance() {
18         if (singleton == null) {
19             singleton = new BadSynchronizedSingleton();
20         }
21         return singleton;
22     }
23 
24     // 驗證單例
25     public static void main(String[] args) {
26         BadSynchronizedSingleton instance1 = BadSynchronizedSingleton.getInstance();
27         BadSynchronizedSingleton instance2 = BadSynchronizedSingleton.getInstance();
28         System.out.println(instance1);
29         System.out.println(instance2);
30     }
31 }
 1 /**
 2  * 雙重加鎖
 3  * JVM創建新的對象時,主要要經過三步。
 4  * 1.分配內存
 5  * 2.初始化構造器
 6  * 3.將對象指向分配的內存的地址
 7  * 這種順序在上述雙重加鎖的方式是沒有問題的,因為這種情況下JVM是完成了整個對象的構造才將內存的地址交給了對象。
 8  * 但是如果2和3步驟是相反的(2和3可能是相反的是因為JVM會針對字節碼進行調優,而其中的一項調優便是調整指令的執行順序),就會出現問題了。
 9  * 因為這時將會先將內存地址賦給對象,針對上述的雙重加鎖,就是說先將分配好的內存地址指給synchronizedSingleton,
10  * 然後再進行初始化構造器,這時候後面的線程去請求getInstance方法時,會認為synchronizedSingleton對象已經實例化了,直接返回一個引用。
11  * 如果在初始化構造器之前,這個線程使用了synchronizedSingleton,就會產生莫名的錯誤。
12  */
13 public class SynchronizedSingleton {
14 
15     // 一個靜態的實例
16     private static SynchronizedSingleton singleton;
17 
18     // 私有化構造函數
19     private SynchronizedSingleton() {
20         super();
21     }
22 
23     // 給出一個公共的靜態方法返回一個單一實例
24     /**
25      *  假設我們去掉同步塊中的是否為null的判斷,
26      *  有這樣一種情況,假設A線程和B線程都在同步塊外面判斷了synchronizedSingleton為null,
27      *  結果A線程首先獲得了線程鎖,進入了同步塊,
28      *  然後A線程會創造一個實例,此時synchronizedSingleton已經被賦予了實例,
29      *  A線程退出同步塊,直接返回了第一個創造的實例,
30      *  此時B線程獲得線程鎖,也進入同步塊,此時A線程其實已經創造好了實例,B線程正常情況應該直接返回的,
31      *  但是因為同步塊裏沒有判斷是否為null,直接就是一條創建實例的語句,
32      *  所以B線程也會創造一個實例返回,此時就造成創造了多個實例的情況。
33      */
34     public static SynchronizedSingleton getInstance() {
35         if (singleton == null) {
36             synchronized (SynchronizedSingleton.class) {
37                 if (singleton == null) {
38                     singleton = new SynchronizedSingleton();
39                 }
40             }
41         }
42         return singleton;
43     }
44 
45     // 驗證單例
46     public static void main(String[] args) {
47         SynchronizedSingleton instance1 = SynchronizedSingleton.getInstance();
48         SynchronizedSingleton instance2 = SynchronizedSingleton.getInstance();
49         System.out.println(instance1);
50         System.out.println(instance2);
51     }
52 
53 }
 1 /** 
 2  * 比較標準的單例模式
 3  * 一個類的靜態屬性只會在第一次加載類時初始化,這是JVM幫我們保證的,所以我們無需擔心並發訪問的問題。
 4  * 所以在初始化進行一半的時候,別的線程是無法使用的,因為JVM會幫我們強行同步這個過程。
 5  * 另外由於靜態變量只初始化一次,所以singleton仍然是單例的。 
 6  * 
 7  * 1.Singleton最多只有一個實例,在不考慮反射強行突破訪問限制的情況下。
 8  * 2.保證了並發訪問的情況下,不會發生由於並發而產生多個實例。
 9  * 3.保證了並發訪問的情況下,不會由於初始化動作未完全完成而造成使用了尚未正確初始化的實例。
10  * 
11  */
12 public class Singleton {
13 
14     private Singleton() {
15         super();
16     }
17 
18     public static Singleton getInstance() {
19         return SingletonInstance.instance;
20     }
21 
22     private static class SingletonInstance {
23         static Singleton instance = new Singleton();
24     }
25 
26 }
 1 /**
 2  * 餓漢式
 3  * 這種方式最主要的缺點
 4  *     就是一旦我訪問了Singleton的任何其他的靜態域,就會造成實例的初始化,而事實是可能我們從始至終就沒有使用這個實例,造成內存的浪費。
 5  *  不過在有些時候,直接初始化單例的實例也無傷大雅,對項目幾乎沒什麽影響,比如我們在應用啟動時就需要加載的配置文件等,就可以采取這種方式去保證單例。 
 6  */
 7 public class Singleton {
 8 
 9     // 一個靜態的實例
10     private static Singleton singleton = new Singleton();
11 
12     // 私有化構造函數
13     private Singleton() {
14         super();
15     }
16 
17     // 給出一個公共的靜態方法返回一個單一實例
18     public static Singleton getInstance() {
19         return singleton;
20     }
21 
22     // 驗證單例
23     public static void main(String[] args) {
24         Singleton instance1 = Singleton.getInstance();
25         Singleton instance2 = Singleton.getInstance();
26         System.out.println(instance1);
27         System.out.println(instance2);
28     }
29 
30 }

01設計模式——單例模式