史上最全的七種單例寫法——單例設計模式
阿新 • • 發佈:2019-05-31
什麼是單例
單例模式確保某各類只有一個例項,而且自行例項化並向整個系統提供這個例項。在計算機系統中,執行緒池、快取、日誌物件、對話方塊、印表機、顯示卡的驅動程式物件常被設計成單例。這些應用都或多或少具有資源管理器的功能,每臺計算機可以有若干個印表機,但只能有一個Printer spooler,以避免兩個列印作業同時輸出到印表機中,每臺計算機可以有若干通訊埠,系統應當集中管理這些通訊埠,以避免一個通訊埠同時被兩個請求同時呼叫。總之,選擇單例模式就是為了避免不一致狀態
單例模式特點
1、單例類只能有一個例項。 2、單例類必須自己建立自己唯一的例項。 3、單例類必須給所有其它物件提供這一例項。
單例模式優缺點
1、單例類只有一個例項
2、共享資源,全域性使用
3、節省建立時間,提高效能
單例模式的七種寫法
分別是「餓漢」、「懶漢(非執行緒安全)」、「懶漢(執行緒安全)」、「雙重校驗鎖」、「靜態內部類」、「列舉」和「容器類管理]
1.餓漢式
package com.xuyu.V1; /** * author:須臾 */ public class SingletonV1 { /** * 餓漢式 * 優點:先天執行緒安全,當類初始化的時候就會建立該物件 * 缺點:如果餓漢式使用頻繁,可能會影響專案啟動效率 */ private static SingletonV1 singletonV1=new SingletonV1(); /** * 將建構函式私有化,禁止初始化 */ private SingletonV1(){} public static SingletonV1 getInstance(){ return singletonV1; } /** * 測試單例 */ public static void main(String[] args) { SingletonV1 instance1 = SingletonV1.getInstance(); SingletonV1 instance2 = SingletonV1.getInstance(); //結果為true,說明保證了單例 System.out.println(instance1==instance2); } }
原始碼分析Runtime
//餓漢式單例
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
..
}
2.懶漢式(執行緒不安全)
package com.xuyu.V2; /** * author:須臾 */ public class SingletonV2 { /** * 餓漢式(執行緒不安全) */ private static SingletonV2 singletonV2; private SingletonV2(){} /** * 建立物件使用 */ public static SingletonV2 getInstance(){ if(singletonV2==null){ try { Thread.sleep(2000); }catch (Exception e){ e.printStackTrace(); } singletonV2=new SingletonV2(); } return singletonV2; } /** * 測試單例 */ public static void main(String[] args) { for (int i=0;i<100;i++){ new Thread(new Runnable() { public void run() { SingletonV2 instance1 = SingletonV2.getInstance(); System.out.println(Thread.currentThread().getName()+","+instance1); } }).start(); } } }
輸出結果:執行緒不安全
Thread-1,com.xuyu.V2.SingletonV2@383a0ba
Thread-4,com.xuyu.V2.SingletonV2@d9d8ad0
Thread-0,com.xuyu.V2.SingletonV2@546431f0
Thread-5,com.xuyu.V2.SingletonV2@2858c11c
Thread-22,com.xuyu.V2.SingletonV2@3635f62a
Thread-6,com.xuyu.V2.SingletonV2@48369750
Thread-7,com.xuyu.V2.SingletonV2@2770f418
Thread-3,com.xuyu.V2.SingletonV2@6d9da26a
Thread-13,com.xuyu.V2.SingletonV2@77355386
Thread-10,com.xuyu.V2.SingletonV2@29580e2d
....
Thread-94,com.xuyu.V2.SingletonV2@3945e031
Thread-91,com.xuyu.V2.SingletonV2@5caf9db6
3.懶漢式(執行緒安全)
package com.xuyu.V3;
/**
* author:須臾
*/
public class SingletonV3 {
/**
* 懶漢式執行緒安全
*/
private static SingletonV3 singletonV3;
private SingletonV3(){}
/**
* 效率低
*/
public synchronized static SingletonV3 getInstance(){
try {
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}
if (singletonV3==null){
System.out.println("建立例項SingletonV3");
singletonV3=new SingletonV3();
}
System.out.println("獲取SingletonV3例項");
return singletonV3;
}
/**
* 測試單例
*/
public static void main(String[] args) {
for (int i=0;i<100;i++){
new Thread(new Runnable() {
public void run() {
SingletonV3 instance1 = SingletonV3.getInstance();
System.out.println(Thread.currentThread().getName()+","+instance1);
}
}).start();
}
}
}
輸出結果
建立例項SingletonV3
獲取SingletonV3例項
Thread-0,com.xuyu.V3.SingletonV3@95458f7
獲取SingletonV3例項
Thread-99,com.xuyu.V3.SingletonV3@95458f7
獲取SingletonV3例項
Thread-98,com.xuyu.V3.SingletonV3@95458f7
獲取SingletonV3例項
Thread-97,com.xuyu.V3.SingletonV3@95458f7
獲取SingletonV3例項
....
4.雙重檢驗鎖(DCL)
package com.xuyu.V4;
public class SingletonV4 {
/**
* volatile 禁止指令重排序
*/
private static volatile SingletonV4 singletonV4;
private SingletonV4(){}
public static SingletonV4 getInstance(){
if(singletonV4==null){//第一次判斷如果沒有建立物件就開始加鎖
synchronized (SingletonV4.class){
if (singletonV4==null){//當用戶搶到鎖,判斷初始化
System.out.println("第一次開始建立例項物件,獲取到鎖了");
try {
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}
singletonV4=new SingletonV4();
}
}
}
return singletonV4;
}
/**
* 測試單例
*/
public static void main(String[] args) {
for (int i=0;i<100;i++){
new Thread(new Runnable() {
public void run() {
SingletonV4 instance1 = SingletonV4.getInstance();
System.out.println(Thread.currentThread().getName()+","+instance1);
}
}).start();
}
}
}
輸出結果:執行緒安全
第一次開始建立例項物件,獲取到鎖了
Thread-99,com.xuyu.V4.SingletonV4@383a0ba
Thread-89,com.xuyu.V4.SingletonV4@383a0ba
Thread-92,com.xuyu.V4.SingletonV4@383a0ba
Thread-91,com.xuyu.V4.SingletonV4@383a0ba
....
Thread-8,com.xuyu.V4.SingletonV4@383a0ba
Thread-6,com.xuyu.V4.SingletonV4@383a0ba
Thread-9,com.xuyu.V4.SingletonV4@383a0ba
Thread-12,com.xuyu.V4.SingletonV4@383a0ba
Thread-11,com.xuyu.V4.SingletonV4@383a0ba
Thread-10,com.xuyu.V4.SingletonV4@383a0ba
Thread-15,com.xuyu.V4.SingletonV4@383a0ba
Thread-19,com.xuyu.V4.SingletonV4@383a0ba
Thread-16,com.xuyu.V4.SingletonV4@383a0ba
5.靜態內部內形式
package com.xuyu.V5;
/**
* author:須臾
*/
public class SingletonV5 {
private SingletonV5(){
System.out.println("物件初始化...");
}
public static SingletonV5 getInstance(){
return SingletonV5Utils.singletonV5;
}
/**
* 靜態內部類方式:能夠避免同步帶來的效率問題和實現懶載入
*/
public static class SingletonV5Utils{
private static SingletonV5 singletonV5=new SingletonV5();
}
/**
* 測試單例
*/
public static void main(String[] args) {
System.out.println("專案啟動成功。。。");
SingletonV5 instance1 = SingletonV5.getInstance();
SingletonV5 instance2 = SingletonV5.getInstance();
System.out.println(instance1==instance2);
}
}
輸出結果
專案啟動成功。。。
物件初始化...
true
6.列舉形式
package com.xuyu.V6;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* author:須臾
*/
public enum EnumSingleton {
INSTANCE;
// 列舉能夠絕對有效的防止例項化多次,和防止反射和序列化破壞
public void add() {
System.out.println("add方法...");
}
/**
* 測試單例
*/
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingleton instance1 = EnumSingleton.INSTANCE;
EnumSingleton instance2 = EnumSingleton.INSTANCE;
System.out.println(instance1==instance2);
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
EnumSingleton v6 = declaredConstructor.newInstance();
System.out.println(v6==instance1);
}
}
輸出結果:反射破壞不了單例
true
Exception in thread "main" java.lang.NoSuchMethodException: com.xuyu.V6.EnumSingleton.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.xuyu.V6.EnumSingleton.main(EnumSingleton.java:19)
7.使用容器管理
package com.xuyu.V7;
import java.util.HashMap;
import java.util.Map;
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String, Object>();
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getService(String key) {
{
return objMap.get(key);
}
}
}
這種使用SingletonManager 將多種單例類統一管理,在使用時根據key獲取物件對應型別的物件。這種方式使得我們可以管理多種型別的單例,並且在使用時可以通過統一的介面進行獲取操作,降低了使用者的使用成本,也對使用者隱藏了具體實現,降低了耦合度。
如何防止破壞單例
雖然單例通過私有建構函式,可以實現防止程式猿初始化物件,但是還可以通過反射和序列化技術破壞單例。
1.使用反射技術破壞單例
// 1. 使用懶漢式建立物件
SingletonV3 instance1 = SingletonV3.getInstance();
// 2. 使用Java反射技術初始化物件 執行無參建構函式
Constructor<SingletonV3> declaredConstructor = SingletonV3.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
SingletonV3 instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);
如何防止被反射破壞
私有建構函式
private SingletonV3() throws Exception {
synchronized (SingletonV3.class) {
if (singletonV3 != null) {
throw new Exception("該物件已經初始化..");
}
System.out.println("執行SingletonV3無參建構函式...");
}
}
2.使用序列化技術破壞單例
Singleton instance = Singleton.getInstance();
FileOutputStream fos = new FileOutputStream("E:\\code\\Singleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("E:\\code\\Singleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Singleton singleton2 = (Singleton) ois.readObject();
System.out.println(singleton2==instance)
//返回序列化獲取物件 ,保證為單例
public Object readResolve() {
return singletonV3;
}
總結
到這裡七中寫法都介紹完了,至於選擇用哪種形式的單例模式,取決於你的專案本身,是否是有複雜的併發環境,還是需要控制單例物件的資源消耗。