詳解單例模式
關於單例模式,話不多說,即程序運行時無論New了多少次,即內存中只有一個實例對象。即對象的HasHCode一致。
單例模式的兩大類
1、餓漢模式(即加載時就創建對象)
-1、直接實例化餓漢模式
-2、靜態代碼塊餓漢模式(即需要加載初始化配置的時候適用)
-3、枚舉方式
2、懶漢式(延遲加載)
-1、單線程安全下的懶漢
-2、多線程安全下的懶漢
-3、靜態內部類的懶漢(安全)
1、直接實例化餓漢模式
package com.single;
public class Singleton {
private static final Singleton SI=new Singleton();
private Singleton(){
}
public static Singleton getsingleton(){
return SI;
}
}
單例模式必須保證自行創建,並且內部提供一個靜態變量來保存這個唯一的對象,構造器私有化,即其他類內部中無法直接New當前的單例類,還需要提供一個對外獲取實例的方法
當前模式在加載類的字節碼的時候當前類的實例就立即會被創建
2、靜態代碼塊餓漢模式(即需要加載初始化配置的時候適用)
1 public class Singleton2 { 2 3 private static final Singleton2 SI; 4 5 private String name; 6 static { 7 Properties properties=new Properties(); 8 try { 9 10 properties.load(Singleton2.class.getClassLoader().getResourceAsStream("db.properties"));11 String name = properties.getProperty("name"); 12 SI=new Singleton2(name); 13 } catch (IOException e) { 14 throw new RuntimeException(e); 15 } 16 17 } 18 19 private Singleton2(String name){ 20 this.name=name; 21 } 22 23 public static Singleton2 getsingleton(){ 24 return SI; 25 }
當我們需要在類創建的時候初始化參數,並且加載某些配置文件的時候可以使用 靜態代碼塊的方式,在創建實例的時候通過構造器來完成相關參數的初始化。
3.枚舉方式的單例,自JDK1.5之後,首先枚舉類和普通類的區別是什麽?
使用enum定義的枚舉類默認繼承了java.lang.Enum類
枚舉類的構造器只能使用private
枚舉類的每個實例必須在枚舉類中顯示的列出(,分隔 ;結尾) 列出的實例系統會自動添加public static final修飾
所有的枚舉類都定義了一個values方法,該方法可以很方便的遍歷所有的枚舉值
可以在switch表達式使用枚舉類對象作為表達式,case子句可以直接使用枚舉的名字,無需添加枚舉類作為限定
枚舉類對象的屬性不能更改,所以要用private final修飾
枚舉類對象要在構造器中被賦值
---------------------
作者:weirdowang
來源:CSDN
原文:https://blog.csdn.net/weirdowang/article/details/79970673
package com.single;
/*
枚舉方式的單例
*/
public enum SingEnum {
SING;
SingEnum(){
}
public void info(){
System.out.println("顯示");
}
}
是不是很簡潔呢? 確實如此,單例模式下 枚舉方式的單例是最簡潔的
二、懶漢模式
1、單線程安全下的懶漢
package com.single2;
/*
懶漢模式
*/
public class Singleton {
private static Singleton singleton;
private Singleton(){
System.out.println("創建");
}
public static Singleton getSingleton(){
if (singleton==null){
singleton=new Singleton();
}
return singleton;
}
}
我們可以看出無論怎麽樣,當前的懶漢都是在調用的時候才被加載創建的,但是它只是在單線程的情況下是安全的為什麽呢? 現在有一個需求即多個線程對這個 單例進行訪問構建對象,在構建對象的時候使當前線程短暫的休眠一下
package com.single2;
/*
懶漢模式測試線程不安全
*/
public class Singleton2 {
private static Singleton2 singleton;
private Singleton2(){
System.out.println("創建");
}
public static Singleton2 getSingleton() throws InterruptedException {
if (singleton==null){
Thread.sleep(100);
singleton=new Singleton2();
}
return singleton;
}
}
測試代碼如下:
/*
懶漢模式下的多線程不安全
*/
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Singleton2 singleton1 = Singleton2.getSingleton();
System.out.println(singleton1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Singleton2 singleton2 = Singleton2.getSingleton();
System.out.println(singleton2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
thread2.start();
}
經過測試,多線程情況下這種是不安全的 為什麽呢? 第一個線程進入 進去之後, 線程休眠100ms 在此期間,第二個線程也開始進去到了創建的方法,由於當前第一個線程正在休眠,所以當前單例為null ,然後第一個線程休眠結束,創建第一個對象,此後第二個線程還在if中,也相繼創建對象,此時便構造成了線程不安全的懶漢單例
2、線程安全的懶漢單例(雙端檢索)
package com.single2;
/*
懶漢模式 雙端檢索,避免了多次線程等待
*/
public class Singleton4 {
private static Singleton4 singleton4;
private Singleton4() {
}
public static Singleton4 getSingleton4() {
if (singleton4 == null) {
synchronized (Singleton4.class) {
if (singleton4 == null) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
singleton4 = new Singleton4();
}
}
}
return singleton4;
}
}
使用synchronized 保證當前只能有一個線程進入 ,並且在外再次進行判斷 若當前的單例已經被創建過了,避免了再次加鎖,直接返回
3、靜態內部類的懶漢單例:(線程安全的)
package com.single2;
public class Singleton5 {
private Singleton5(){
}
private static class demo{
private static final Singleton5 SINGLETON_5=new Singleton5();
}
public static Singleton5 singleton5(){
return demo.SINGLETON_5;
}
}
也是懶漢模式最簡單的單例實現,靜態內部類不會隨著外部類的初始化而初始化,並且內部類具有自己的類加載器,是安全的
如上便是單例模式的六種創建方式,歡迎大牛指正,源碼放到群裏了 另外也歡迎各位朋友們一起學習交流, qq群:956809929
詳解單例模式