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

前言

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

正文

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

懶漢式

        懶漢式指的是在服務啓動時並不建立單例的實例,而是在須要時建立,而且爲了保證線程安全,在建立時方法設置了同步鎖。spring

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;
    }
}
複製代碼

枚舉類型

        上述的一些方式均可能會被反射等方式破壞單例,所以又衍生出了枚舉的單例,以下:bash

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日誌管理框架等。mybatis

spirng框架

        對於最經常使用的spring框架來講,咱們常常用spring來幫咱們管理一些無狀態的bean,其默認設置爲單例,這樣在整個spring框架的運行過程當中,即便被多個線程訪問和調用,這些「無狀態」的bean就只會存在一個,爲他們服務。那麼「無狀態」bean指的是什麼呢?多線程

        無狀態:當前咱們託管給spring框架管理的javabean主要有service、mybatis的mapper、一些utils,這些bean中通常都是與當前線程會話狀態無關的,沒有本身的屬性,只是在方法中會處理相應的邏輯,每一個線程調用的都是本身的方法,在本身的方法棧中。app

        有狀態:指的是每一個用戶有本身特有的一個實例,在用戶的生存期內,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…

結語

        本文對單例模式進行了簡單的總結,及目前生態中單例的實用場景,你們若是有好的實現方式或者更多的實用場景能夠補充一下。

相關文章
相關標籤/搜索