1. 程式人生 > 程式設計 >聊一聊單例及框架中的單例

聊一聊單例及框架中的單例

前言

        單例模式是設計模式中最簡單也是最常用的設計模式之一,單例顧名思義就是系統中只有唯一例項,這個唯一例項的獲取方式就是通過一個方法的呼叫獲得,而不是通過正常流程中的new例項化。多年前在學習設計模式時就瞭解到單例有多種實現方式,今天就來總結一下,並且探索一下在當前java生態框架中的應用場景。

正文

        先回顧一下單例的幾種實現方式,懶漢式、餓漢式、列舉實現。

懶漢式

        懶漢式指的是在服務啟動時並不建立單例的例項,而是在需要時建立,並且為了保證執行緒安全,在建立時方法設定了同步鎖。

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton
(){} public static synchronized LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } } 複製程式碼

        還存在一種對上面的改進,為了提升效率,防止多個執行緒訪問getSingleton時的,只是在建立單例例項時再去加同步鎖。

public class LazySingletonV2 {
    private volatile static LazySingletonV2 singleton;
    private LazySingletonV2
(){} public static LazySingletonV2 getSingleton() { if (singleton == null) { synchronized (LazySingletonV2.class) { if (singleton == null) { singleton = new LazySingletonV2(); } } } return singleton; } } 複製程式碼

餓漢式

        餓漢式則比較直接,在服務啟動時就已經初始化好了單例例項,比較推薦。

public class HunSingleton {

    private static HunSingleton instance = new HunSingleton();

    private HunSingleton() {
    }

    public static HunSingleton getInstance() {
        return instance;
    }
}
複製程式碼

列舉型別

        上述的一些方式都可能會被反射等方式破壞單例,因此又衍生出了列舉的單例,如下:

public class EnumSingleton {
    private EnumSingleton(){}
    public static EnumSingleton getInstance(){
        return Singleton.INSTANCE.getInstance();
    }
    private static enum Singleton{
        INSTANCE;

        private EnumSingleton singleton;
        //JVM會保證此方法絕對只呼叫一次
        private Singleton(){
            singleton = new EnumSingleton();
        }
        public EnumSingleton getInstance(){
            return singleton;
        }
    }
}
複製程式碼

應用場景

        上述是幾種單例的簡單實現方式,那Java生態中也有很多框架用到了單例模式。如spring ioc的bean管理、log4j日誌管理框架等。

spirng框架

        對於最常用的spring框架來說,我們經常用spring來幫我們管理一些無狀態的bean,其預設設定為單例,這樣在整個spring框架的執行過程中,即使被多個執行緒訪問和呼叫,這些“無狀態”的bean就只會存在一個,為他們服務。那麼“無狀態”bean指的是什麼呢?

        無狀態:當前我們託管給spring框架管理的javabean主要有service、mybatis的mapper、一些utils,這些bean中一般都是與當前執行緒會話狀態無關的,沒有自己的屬性,只是在方法中會處理相應的邏輯,每個執行緒呼叫的都是自己的方法,在自己的方法棧中。

        有狀態:指的是每個使用者有自己特有的一個例項,在使用者的生存期內,bean保持了使用者的資訊,即“有狀態”;一旦使用者滅亡(呼叫結束或例項結束),bean的生命期也告結束。即每個使用者最初都會得到一個初始的bean,因此在將一些bean如User這些託管給spring管理時,需要設定為prototype多例,因為比如user,每個執行緒會話進來時操作的user物件都不同,因此需要設定為多例。

        但spring框架在實現對bean管理時,跟上述的懶漢、餓漢均不相同,是通過ConcurrentHashMap單例登入檔的方式實現的,在這就不再多闡述。

        單例bean的一些優勢與劣勢:

優勢: 1.減少了新生成例項的消耗,spring會通過反射或者cglib來生成bean例項這都是耗效能的操作,其次給物件分配記憶體也會涉及複雜演演算法; 2.減少jvm垃圾回收; 3.可以快速獲取到bean;

劣勢: 單例的bean一個最大的劣勢就是要時刻注意執行緒安全的問題,因為一旦有執行緒間共享資料變很可能引發問題。

log4j

        在使用log4j框架時也注意到了其使用的是單例,當然也為了保證單個執行緒對日誌檔案的讀寫時不出問題,與使用spring管理bean的目標不是相似,如下為其logfactory單例建立的原始碼:

class Log4jLoggerFactory {
    private static ConcurrentMap<String,Logger> log4jLoggers = new ConcurrentHashMap();

    Log4jLoggerFactory() {
    }

    public static Logger getLogger(String name) {
        Logger instance = (Logger)log4jLoggers.get(name);
        if (instance != null) {
            return instance;
        } else {
            Logger newInstance = new Logger(name);
            Logger oldInstance = (Logger)log4jLoggers.putIfAbsent(name,newInstance);
            return oldInstance == null ? newInstance : oldInstance;
        }
    }

    public static Logger getLogger(String name,LoggerFactory loggerFactory) {
        Logger instance = (Logger)log4jLoggers.get(name);
        if (instance != null) {
            return instance;
        } else {
            Logger newInstance = loggerFactory.makeNewLoggerInstance(name);
            Logger oldInstance = (Logger)log4jLoggers.putIfAbsent(name,newInstance);
            return oldInstance == null ? newInstance : oldInstance;
        }
    }
}
複製程式碼

        既然log4j使用單例對單個檔案讀寫,那麼如何實現多執行緒環境下同時讀寫多個檔案,這個博文實現了對每個執行緒獨立的log檔案的讀寫,blog.csdn.net/guan0005/ar…

結語

        本文對單例模式進行了簡單的總結,及目前生態中單例的實用場景,大家如果有好的實現方式或者更多的實用場景可以補充一下。