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

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

聯繫方式:QQ:1441289873

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

單例模式:單例對象能保證在一個JVM中,該對象只有一個實例存在。

先簡單說一下,各自的區別:

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

  1. 餓漢式(線程安全,調用效率高;但是不能延時加載)
  2. 懶漢式(線程安全,調用效率不高,但是可以延時加載)

其他:

  1. 雙重檢測鎖式(由於JVM底層內部模型原因,偶爾會出問題,不建議使用)
  2. 靜態內部類式(線程安全,調用效率高,但是,可以延時加載)
  3. 枚舉類(線程安全,調用效率高,防反射和反序列化,不能延時加載)如何選用?
  4. 單例對象佔用資源少,不需要延時加載時:枚舉類好於餓漢式 - 單例對象佔用資源大,需要延時加載時:

靜態內部類式好於懶漢式

資料來源

一、接下來從最簡單的單例模式開始吧直接上代碼:


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());
            }
    }
}

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

多線程下:com.example.a14412.myapplication.Android@2957fcb0
多線程下:com.example.a14412.myapplication.Android@475cf196

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

多線程下:com.example.a14412.myapplication.Android@1a9841c0
多線程下:com.example.a14412.myapplication.Android@1a9841c0

不要緊,我來解釋一下原因。第一次的結果是這樣來的。因爲代碼的執行速度很快,即便是兩個線程。一個線程走到如果判斷時,另一個也到了,如果判斷,他們兩個都得到對象是空的結果,就各自實例化了對象。(這也是線程不安全的原因)

第二次的結果原因大家應該理解了,就是說,一個線程快,一個線程慢,快的實例化後對象。第二個線程才判斷,此時的對象已經被實例化過了,所以如果判斷是不等於空。

線程的速度爲什麼會快慢呢,這時的你需要理解多線程去了。

2.加鎖的單例模式

在多線程模式下,大家能想到第一個就是同步的,這個關鍵字吧。這個關鍵字我就不在這說了。直接上代碼吧!

這個單例是爲了彌補上面單例在多線程下實例了兩個對象的不足。

public class Android {
    private static Android android = null;

    private Android() {
    }

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

}

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

第一次:

多線程下:com.example.a14412.myapplication.Android@6b5b9135
多線程下:com.example.a14412.myapplication.Android@6b5b9135

第二次:
多線程下:com.example.a14412.myapplication.Android@1a9841c0

第三次

 

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

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

遇到了,我們繼續。

原因很簡單啊,主線程先結束了,子線程執行不到打印的代碼了。修改加個睡眠試試。

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());
            }
    }
}

打印結果:

第一次:

多線程下:com.example.a14412.myapplication.Android@1a9841c0
多線程下:com.example.a14412.myapplication.Android@1a9841c0

第二次:

多線程下:com.example.a14412.myapplication.Android@6b5b9135
多線程下:com.example.a14412.myapplication.Android@6b5b9135

第三次:

多線程下:com.example.a14412.myapplication.Android@2608dd6c
多線程下:com.example.a14412.myapplication.Android@2608dd6c

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

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;
    }

}

雙重如果判斷,這是已經搞定了。但是這個單例還是不推薦。如果雙重,容易出問題。在程序大了,代碼複雜了,就不是這麼簡單的使用了。說不定會出現什麼未知情況,比如死鎖啊,等待。以上都是懶漢式是單例。(延時加載)想繼續瞭解看這篇的Java的單例模式中雙重檢查鎖的問題

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

4.內部類單例

先上代碼:

public class Android {

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

}

內部類的寫法。在類裏寫個內部類。由於內部類是私有的只有Androi可以訪問的。那怎麼訪問,這不是還有一個public static的方法嗎?由於內部類是靜的,就可以使用AndroidHolder.android獲取的就是新的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:com.example.a14412.myapplication.Android@29abcaca
多線程下1:com.example.a14412.myapplication.Android
多線程下1:com.example.a14412.myapplication.Android@29abcaca
多線程下1 :com.example.a14412.myapplication.Android

 

單例模式的優點:

1.在內存中只有一個實例,減少內存開支

2.只生產一個實例,減少系統性能的性能開銷

3.避免對資源的多重佔用。

4.可以在系統設置全局的訪問點,優化和共享資源訪問。(例如可以設置一個單例類,負責所有數據表的映射處理)

單例的缺點;

1.單例一般沒有接口,擴展很困難。

2.單例如果持有Context對象,很容易引起內存泄漏,最好傳遞全局的Application Context。
 

單例模式到這就結束了,安卓開發的有福利了。送你給插件,SingletonTest。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章