1. 程式人生 > >java單列模式(從0開始分析)

java單列模式(從0開始分析)

Java中的單例模式的資料已經有很多了,很優秀的也有許多,但是我的部落格,還是為小白寫的。(因為我也是小白啊!)希望你可以按我的步驟一起學習一下,我已經寫的很詳細了,寫一遍大於看一遍!!!如果你是大神也不要緊,你可以直接看這篇部落格(我大部分想法都來源於他),或者幫我看看我有沒有錯,也可以看看有沒有用當年沒有搞清楚的。----(寫所有給看的人)最重要的是希望你能幫我看看哪裡不對,或者有問題、不合理都可以告訴我,我們一起學習一下。(謝謝啦)

聯絡方式:QQ:1441289824 

————————————————————————華麗的分割線—————————————————————————

java中有眾多設計模式,其中最常用的就是單例模式。大家肯定看到許多單例模式和用過,接下我先說一下單例模式的區別和用法,然後從簡單的開始學習。

單例模式:單例物件能保證在一個JVM中,該物件只有一個例項存在。

常見的五種單例模式實現方式 主要:

-餓漢式(執行緒安全,呼叫效率高;但是不能延時載入)

-懶漢式(執行緒安全,呼叫效率不高,但是可以延時載入)

其他: -雙重檢測鎖式(由於JVM底層內部模型原因,偶爾會出問題,不建議使用) -靜態內部類式(執行緒安全,呼叫效率高,但是,可以延時載入)

-列舉類(執行緒安全,呼叫效率高,防反射和反序列化,不能延時載入) 如何選用?

-單例物件 佔用資源少,不需要延時載入時: 列舉類 好於 餓漢式 -單例物件 佔用資源大,需要延時載入時:

靜態內部類式 好於 懶漢式

資料來源

1.接下來從最簡單的單例模式開始吧!直接上程式碼:


public class Android {

   /*宣告一個變數等於null,實現了延遲載入*/
    private static Android android = null;

    /*構造方法私有化,防止被new出來*/
    private Android() {

    }

     /*呼叫這個方法時判斷物件不存在時建立物件*/

    public static Android getinstance() {
        if (android == null) {
            android = new Android();
        }
        return android;
    }

}

大部分看過單例文章,都知道這個單例執行緒不安全唄。為什麼不安全呢,當時小白的我就沒有搞懂,尤其是我這種自學的單例,只是抄別人的,什麼都不懂。(哈哈)

哪裡不安全呢,我給大家一段測試程式碼,你就可以看出來了。

public class ExampleUnitTest {


    @Test
    public void test() {
        MyThread myThread =new MyThread();
        MyThread myThread1 =new MyThread();
        myThread.start();
        myThread1.start();
    }

        class MyThread extends Thread {
            @Override
            public void run() {
                Android getinstance = Android.getinstance();
                System.out.println("多執行緒下: " + getinstance.toString());
            }
    }
}

我的第一次接下是測試結果:

多執行緒下: [email protected] 多執行緒下: [email protected]

有同學說了他的結果和我一樣是這樣的:

多執行緒下: [email protected] 多執行緒下: [email protected]

不要緊,我來解釋一下原因。第一次的結果是這樣來的。因為程式碼的執行速度很快,即便是兩個執行緒。一個執行緒走到if判斷時,另一個也到了if判斷,他們兩個都得到物件是null的結果,就各自例項化了物件。(這也是執行緒不安全的原因)

第二次的結果原因大家應該理解了,就是說。一個執行緒快,一個執行緒慢,快的例項化後物件。第二個執行緒才判斷,此時的物件已經被例項化過了,所以if判斷是不等於null。

執行緒的速度為什麼會快慢呢,這時的你需要理解多執行緒去了。

2.加鎖的單例模式

在多執行緒模式下,大家能想到第一個就是synchronized,這個關鍵字吧。這個關鍵字我就不在這說了。直接上程式碼吧!

這個單例是為了彌補上面單例在多執行緒下例項了兩個物件的不足。

public class Android {
    private static Android android = null;

    private Android() {
    }

    public synchronized static Android getinstance() {
        if (android == null) {
            android = new Android();
        }
        return android;
    }

}

對,基本沒有變化。只是加了一個synchronized的關鍵字而已。看測試效果:

第一次:

多執行緒下: [email protected] 多執行緒下: [email protected]

第二次: 多執行緒下: com.example.a14412.myapplication.Andro[email protected]

第三次

已經結束了,對你猜對了,第三次沒有列印任何東西。你說你是不這樣的,好吧。你先看看和我的程式碼一樣嗎?

如果一樣的話,請多執行幾次,看看。這三種結果,都遇到了嗎?

遇到了,我們繼續。

原因很簡單啊,主執行緒先結束了,子執行緒執行不到列印的程式碼了。修改加個sleep試試。

public class ExampleUnitTest {


    @Test
    public void test() {
        MyThread myThread =new MyThread();
        MyThread myThread1 =new MyThread();
        myThread.start();
        myThread1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

        class MyThread extends Thread {
            @Override
            public void run() {
                Android getinstance = Android.getinstance();
                System.out.println("多執行緒下: " + getinstance.toString());
            }
    }
}

列印結果:

第一次:

多執行緒下: [email protected] 多執行緒下: [email protected]

第二次:

多執行緒下: [email protected] 多執行緒下: [email protected]

第三次:

多執行緒下: [email protected] 多執行緒下: [email protected]

這個單例,沒有問題了?但是我們可以優化的。為什麼要優化呢。從效能上看。當一個執行緒進入能得到同步鎖,加上鎖以後,第二個執行緒只能等待。如果這時那個執行緒已經例項化物件,由於有同步鎖,另一個執行緒還要等待。小程式就沒事了,如果程式大了。每次例項化這個物件,都要有些執行緒在等待。,就會很耗時和耗效能。上程式碼

3.雙重檢測鎖式

public class Android {
    private static Android android = null;

    private Android() {
    }

    public  static Android getinstance() {
        if (android == null) {
            synchronized (Android.class) {
                if (android==null) {
                    android = new Android();
                }
            }
        }
        return android;
    }

}

雙重if判斷,這是已經搞定了。但是這個單例還是不推薦。雙重if,容易出問題。在程式大了,程式碼複雜了,就不是這麼簡單的使用了。說不定會出現什麼未知情況,比如死鎖啊,等待。以上都是懶漢式是單例。(延時載入)

那我也就不羅嗦了,寫我最終版的單例模式吧!

4.內部類單例

先上程式碼:

public class Android {

    private Android() {
    }
    public static Android getAndroidHolder(){
        return AndroidHolder.android;
    }
    private static  class AndroidHolder{
        private final static Android android=new Android();
    }

}

內部類的寫法。在類裡寫個內部類。由於內部類是private 的只有Androi可以訪問的。那怎麼訪問,這不是還有一個public static  的方法嗎?由於內部類是 static 的,就可以使用 AndroidHolder.android獲取的就是new Android這個物件了。

看測試程式碼:

public class ExampleUnitTest {


    @Test
    public void test() {
        MyThread myThread =new MyThread();
        MyThread myThread1 =new MyThread();
        myThread.start();
        myThread1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

        class MyThread extends Thread {
            @Override
            public void run() {
                Android androidHolder = Android.getAndroidHolder();
                //列印的是地址值
                System.out.println("多執行緒下1: " + androidHolder.toString());
                //列印的是類名
                System.out.println("多執行緒下1: " + androidHolder.getClass().getName());
            }
    }

再看結果:

多執行緒下1: [email protected] 多執行緒下1: com.example.a14412.myapplication.Android 多執行緒下1: [email protected] 多執行緒下1: com.example.a14412.myapplication.Android

單例模式到這就結束了,Android開發的有福利了。送你給外掛,SingletonTest.

SingletonTest用法很簡單,百度一下就好。