簡言
單列模式:單一,也就是說一個類只能有一個對象。就類似於有些軟件只能打開一次,當需要保證一個對象在內存中的唯一性時,就需要引入單列模式。
實現步驟
創建單列模式分三步操作:
1.將構造函數私有化
2.在類中創建一個本類對象
3.提供一個公有的接口來返回創建的類
相關說明
我們訪問類裏面的數據時,分兩種情況:
1.通過實例化對象,然後通過對象的引用“.”出方法或屬性。
2.通過類名“.”調用類裏面靜態的方法或屬性。
當我們將類的構造方法私有化時,此時我們無法在類外實例化對象,所以只能通過第二種方法在內部創建一個static修飾的該類對象,然後定義一個static修飾的公有的方法將創建的類返回出去。static修飾的數據是跟隨整個類的,在類加載的時候跟着類一起創建,由於構造函數私有化了,所以這個類也就創建了一次。
餓漢式
創建一個Singlehungry類
package com.single_mode;
/**
* 單列模式:一個類只能被創建一次,將類的構造方法私有化,提供公有的返回該對象的方法
*
* 餓漢式:加載類的時候就創建了創建了一個Single實例
*/
public class Singlehungry {
private String name;
private int age;
public String getName() { return name; }
public int getAge() { return age; }
public void setName(String name) { this.name = name; }
public void setAge(int age) { this.age = age; }
private Singlehungry(){ }//將構造函數私有化
private static Singlehungry single=new Singlehungry();
public static Singlehungry getInstance(){
return single;
}//提供公有訪問的接口,將該對象返回出去
}
創建一個測試類Main類
package com.single_mode;
public class Main{
public static void main(String[] args) {
//餓漢式
Singlehungry single1=Singlehungry.getInstance();
single1.setName("筱靜");
single1.setAge(20);
System.out.println("single1:"+single1.getAge()+" "+single1.getName());
Singlehungry single2=Singlehungry.getInstance();
System.out.println("single2:"+single2.getAge()+" "+single2.getName());
}
}
運行結果:
single1和single2是指向的同一個對象
讓我們畫張圖來了解一下:
當我們Singlehungry.加載類時,這個類的靜態屬性在方法區也就隨着加載,首先在堆區new了一個Singlehungry的對象,假設它的地址爲0x10,方法區的single保存了堆區Singlehungry對象的地址,即single=0x10。然後在棧區調用getInstance()方法,返回single,並用single1來接收,single1也就指向堆區的Singlehungry對象,然後set進行賦值。再次調用getInstance()方法時,調用之前加載好的Singlehungry對象,也就是single2=single=0x10。所以再次get時,它的值是相同的。
懶漢式
創建一個Singlelazy類
package com.single_mode;
/**
* 懶漢式:
*/
public class Singlelazy {
private String name;
private int age;
private static Singlelazy singlelazy=null;
private Singlelazy(){ }
// public static Singlelazy getInstence(){ //線程不安全
// if(singlelazy==null)
// singlelazy=new Singlelazy();
// return singlelazy;
// }
public static Singlelazy getInstence(){
if(singlelazy==null) {
synchronized(Singlelazy.class) {
if (singlelazy == null)
singlelazy = new Singlelazy();
}
}
return singlelazy;
}
public String getName() { return this.name; }
public int getAge() { return age; }
public void setName(String name) { this.name = name; }
public void setAge(int age) { this.age = age; }
}
在我們用懶漢式發生了延時加載,首先方法區裏面singlelazy賦值爲null,在調用getInstence()方法時先判斷一下,然後創建對象,而餓漢式在加載類的同時single就在堆區new了一個對象,這也是它們的區別所在。
由於方法區是線程共享的,懶漢式又有if判斷然後創建對象。因此在多線程的情況下可能存在線程安全問題,需要線程同步用到synchonized關鍵字。如果將getInstence()整個函數個鎖住,開銷可能比較大,不太可取。可直接將判斷那部分鎖住即可。
或許你會問爲什需要加上兩個if判斷?
假設有A,B,C三個線程,首先A線程搶到了CPU的執行權,執行 singlelazy==null 再執行下一個singlelazy==null 並且鎖上,當A剛第二次判斷時,CPU的執行權可能被C搶到了(CPU高速不斷的切換線程,給我們的感覺每個進程異步執行),C線程進行第一層判斷,由於第二層if上鎖了,在A線程執行完畢之前 ,C線程無法繼續執行的,只能等待了。C在等待的過程中CPU可能又切到了A線程,A線程接着執行,當A線程創建對象後,把鎖打開 return singlelazy;。B線程得到了CPU的執行權,第一輪if判斷結束false 就直接return singlelazy;最後C線程的到CPU執行權,由於A線程已把鎖打開,C線程繼續剛纔的執行,進行第二次判斷false,return singlelazy;最終得到的是A線程所創建的一個對象,解決了線程互斥問題。
每日雞湯:耐得住寂寞、經得起誘惑。你會發現通往成功的道路並不擁擠,因爲能堅持到最後的沒有幾個!
Over !