Java描述設計模式(01):單例模式

一、單例模式

1、概念圖解

單例設計模式定義:確保這個類只有一個實例,並且自動的實例化向系統提供這個對象。

2、樣例代碼

package com.model.test;
public class Singleton {
    // 使用靜態變量記錄唯一實例
    private static Singleton singleton = null;
    private Singleton (){}
    public static Singleton getInstance (){
        if (singleton == null){
            singleton = new Singleton() ;
        }
        return singleton ;
    }
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance() ;
        Singleton singleton2 = Singleton.getInstance() ;
        /**
         * com.model.test.Singleton@15db9742
         * com.model.test.Singleton@15db9742
         */
        System.out.println(singleton1);
        System.out.println(singleton2);
    }
}

Singleton稱爲單例類,構造函數使用private修飾,確保系統中只能產生一個實例,並且自動生成的。上面代碼也就是所謂的懶漢式加載:只有到使用該對象的時候纔來創建,意思餓了纔來做飯吃。

二、線程安全問題

在上面的代碼中存在一個很明顯的線程安全問題,當有多條線程來請求對象實例的時候,因爲對象的創建是需要時間的,假設A線程進來判斷singleton == null,就會進入對象的創建過程,這時如果同時在過來幾條線程,那麼他們都會得到一個對象實例,這個就是所謂的線程安全問題。

1、同步控制方式

package com.model.test;
public class Singleton {
    // 使用靜態變量記錄唯一實例
    private static Singleton singleton = null;
    private Singleton (){}
    public static synchronized Singleton getInstance (){
        if (singleton == null){
            singleton = new Singleton() ;
        }
        return singleton ;
    }
}

這樣操作會影響系統性能

2、餓漢式加載

public class Singleton {
    // 使用靜態變量記錄唯一實例
    private static Singleton singleton = new Singleton();
    private Singleton (){}
    public static Singleton getInstance (){
        return singleton ;
    }
}

這裏先把對象創建出來,有需要直接使用;

3、雙重檢查

public class Singleton {
    // 使用靜態變量記錄唯一實例
    // volatile可以確保當singleton被初始化後,多線程纔可以正確處理
    // 被volatile修飾的變量的值,將不會被本地線程緩存
    // 對該變量讀寫都是直接操作共享內存,確保多個線程能正確的處理該變量。
    private static volatile Singleton singleton = null ;
    private Singleton (){}
    public static Singleton getInstance (){
        // 如果實例不存在,則進入同步區
        if (singleton == null){
            // 只有第一次纔會徹底執行這裏面的代碼
            synchronized (Singleton.class) {
                if (singleton == null){
                    singleton = new Singleton() ;
                }
            }
        }
        return singleton ;
    }
}

4、枚舉方式

package com.model.design.base.node01.singleton;
import org.junit.Test;
/**
 * 類級內部類裏面創建對象實例
 */
public class C06_Singleton {
 @Test
 public void test01 (){
 SingletonDemo INSTANCE1 = SingletonDemo.INSTANCE ;
 SingletonDemo INSTANCE2 = SingletonDemo.INSTANCE ;
 System.out.println(INSTANCE1 == INSTANCE2);
 INSTANCE1.info();
 INSTANCE2.info();
 }
}
enum SingletonDemo {
 INSTANCE ;
 public void info (){
 System.out.println("枚舉方式實現單例");
 }
}

三、延遲類初始化

1、基礎概念

1)、類級內部類
  簡單點說,類級內部類指的是,有static修飾的成員式內部類。如果沒有static修飾的成員式內部類被稱爲對象級內部類。
  類級內部類相當於其外部類的static成分,它的對象與外部類對象間不存在依賴關係,因此可直接創建。而對象級內部類的實例,是綁定在外部對象實例中的。
  類級內部類中,可以定義靜態的方法。在靜態方法中只能夠引用外部類中的靜態成員方法或者成員變量。
  類級內部類相當於其外部類的成員,只有在第一次被使用的時候才被會裝載。

2)、多線程缺省同步鎖
  在多線程開發中,爲了解決併發問題,主要是通過使用synchronized來加互斥鎖進行同步控制。但是在某些情況中,JVM已經隱含地執行了同步,這些情況下就不用自己再來進行同步控制了。這些情況包括:

  1.由靜態初始化器(在靜態字段上或static{}塊中的初始化器)初始化數據時
  2.訪問final字段時
  3.在創建線程之前創建對象時
  4.線程可以看見它將要處理的對象時

2、實現方式

要想很簡單地實現線程安全,可以採用靜態初始化器的方式,它可以由JVM來保證線程的安全性。比如前面的餓漢式實現方式,在類裝載的時候就初始化對象,不管是否需要,存在一定的空間浪費。
一種可行的方式就是採用類級內部類,在這個類級內部類裏面去創建對象實例。這樣一來,只要不使用到這個類級內部類,那就不會創建對象實例,從而同時實現延遲加載和線程安全。

public class LazySingleton {
    /**
     * 類級內部類
     */
    private static class SingletonHolder {
        private static LazySingleton lazySingleton = new LazySingleton() ;
    }
    public static LazySingleton getInstance (){
        return SingletonHolder.lazySingleton ;
    }
    public static void main(String[] args) {
        LazySingleton lazySingleton1 = LazySingleton.getInstance() ;
        LazySingleton lazySingleton2 = LazySingleton.getInstance() ;
        /**
         * com.model.test.LazySingleton@15db9742
         * com.model.test.LazySingleton@15db9742
         */
        System.out.println(lazySingleton1+";;"+lazySingleton2);
    }
}

四、JDK源碼單例模式

Runtime單例實現源碼。

1、案例演示

/**
 * JDK 單例模式分析
 */
public class C07_Singleton {
 public static void main(String[] args) {
 Runtime runtime1 = Runtime.getRuntime() ;
 Runtime runtime2 = Runtime.getRuntime() ;
 /*
 * 1229416514
 * 1229416514
 */
 System.out.println(runtime1.hashCode());
 System.out.println(runtime2.hashCode());
 }
}

2、源代碼分析

public class Runtime {
 private static Runtime currentRuntime = new Runtime();
 public static Runtime getRuntime() {
 return currentRuntime;
 }
 private Runtime() {}
}

基於餓漢模式實現的單例模式。

五、Spring框架中應用

1、創建測試類

public class UserBean {
}

2、Spring配置文件

<!-- 單例Bean -->
<bean id="user" 
class="com.model.design.spring.node01.singleton.UserBean" />

3、測試讀取Bean對象

package com.model.design.spring.node01.singleton;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * Spring框架中單例模式
 */
public class S01_Singleton {
    @Test
    public void test01 (){
        ApplicationContext context01 = new ClassPathXmlApplicationContext("/spring/spring-context.xml");
        ApplicationContext context02 = new ClassPathXmlApplicationContext("/spring/spring-context.xml");
        UserBean user01 = (UserBean)context01.getBean("user") ;
        UserBean user02 = (UserBean)context01.getBean("user") ;
        UserBean user03 = (UserBean)context02.getBean("user") ;
        // com.model.design.spring.node01.singleton.UserBean@364841
        System.out.println(user01);
        // com.model.design.spring.node01.singleton.UserBean@364841
        System.out.println(user02);
        // com.model.design.spring.node01.singleton.UserBean@c4711c
        System.out.println(user03);
    }
}

結論
Spring單例模式與純粹的單例設計模式的主要區別
儘管使用相同的類加載器來加載兩個應用程序上下文,但是UserBean的實例是不一樣的。也就是Spring框架中的單例對象是基於應用程序中。

六、單例模式總結

1、注意事項

單例模式注意事項和細節說明
1) 單例模式保證了 系統內存中該類只存在一個對象,節省了系統資源,對於一些需要頻繁創建銷燬的對象,使用單例模式可以提高系統性能。
2) 當想實例化一個單例類的時候,必須要記住使用相應的獲取對象的方法,而不是使用new Object() 的方式。
3) 單例模式使用的場景:需要頻繁的進行創建和銷燬的對象、創建對象時耗時過多或耗費資源過多(即:重量級對象),但又經常用到的對象。

2、優缺點

優點:
1、單例模式只會創建一個對象實例,減少內存消耗
2、設置全局訪問點,優化共享資源的訪問
缺點:
1、沒有接口,很難擴展
2、不利於測試
3、與單一職責原則衝突

七、源代碼地址

GitHub地址:知了一笑
https://github.com/cicadasmile/model-arithmetic-parent
碼雲地址:知了一笑
https://gitee.com/cicadasmile/model-arithmetic-parent
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章