單例模式是簡單的設(shè)計模式之一,屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的方式,確保只有單個對象被創(chuàng)建。這個設(shè)計模式主要目的是想在整個系統(tǒng)中只能出現(xiàn)類的實例,即一個類只有一個對象。單列模式的解決的痛點就是節(jié)約資源,節(jié)省時間從兩個方面看:
1. 由于頻繁使用的對象,可以省略創(chuàng)建對象所花費的時間,這對于那些重量級的對象而言,是很重要的。
2. 因為不需要頻創(chuàng)建對象,我們的GC壓力也減輕了,而在GC中會有STW(stop the world), 從這一方面也節(jié)約了GC的時間,單例模式的缺點:簡單的單例模式設(shè)計開發(fā)都比較簡單,但是復(fù)雜的單例模式需要考慮線程安全等并發(fā)問題。
單例類什么時候被初始化呢? 類加載的時候就會被初始化,java虛擬機(jī)規(guī)范嚴(yán)格規(guī)定了有且只有四種情況必須立即對類進(jìn)行初始化,遇到new、getStatic、putStatic、invokeStatic這4條字節(jié)碼指令時,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。生成這四條指令最常見的java代碼場景:
1> 使用new關(guān)鍵字實例化對象
2> 讀取一個類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放在常量池的靜態(tài)字段除外)
3> 設(shè)置一個類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放在常量池的靜態(tài)字段除外)
4> 調(diào)用一個類的靜態(tài)方法
現(xiàn)在我們已知道類初始化的四種情況了。
那么我們來列舉:八種單例實現(xiàn)方式,再分析其特點。
1. 餓漢式靜態(tài)塊單例:
public class HungryStaticSingleton{
? ?private static final?HungryStaticSingleton instance;
? ?static {
? ? ? instance = new HungryStaticSingleton();
? ?}
? ?private?HungryStaticSingleton(){}
? ?public static?HungryStaticSingleton getInstance(){return instance;}
2. 懶漢式雙重檢查單例:
public class LazyDoubleCheckSingleton{
? ? private static volatile static?LazyDoubleCheckSingleton lazy = null;
? ? private?LazyDoubleCheckSingleton(){}
? ? public static?LazyDoubleCheckSingleton getInstance() {
? ? ? ? ?if (null == lazy) {
? ? ? ? ? ? ?synchronized(LazyDoubleCheckSingleton.class) {
? ? ? ? ? ? ? ? ? if (null ==? lazy) {
? ? ? ? ? ? ? ? ? ? ? lazy = new?LazyDoubleCheckSingleton();
? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ?return lazy;
? ? ? }
/**
加synchronized不必多說了,保證線程同步,原子性。
那為什么在靜態(tài)常量中加volatile呢?
1. volatile 是在內(nèi)存中可見性的。
2. 防止指令重排序:也就是說在new?LazyDoubleCheckSingleton時指令重排序?qū)е缕渌€程獲取到未初始化完的對象。
? ? instance = new?LazyDoubleCheckSingleton()這句,這并非是一個原子操作。事實上在JVM中這句話大概做了下面三件? ? ?事。
? ? ? ? ?1> lazy分配內(nèi)存,
? ? ? ? ?2> 調(diào)用LazyDoubleCheckSingleton構(gòu)造函數(shù)來初始化成員變量
? ? ? ? ?3>? 將lazy對象指向分配的內(nèi)存空間(執(zhí)行完之后就不再為null了),但是在JVM的即時編譯期中存在指令排序的優(yōu)化。
? ? ? ? ? ? ? 也就是說2> 和3> 的順序是不能保證的,最終的執(zhí)行順序可能是1>2>3也有可能是1>3>2,如果是1>3>2的話,? ? ? ? ? ? ? ? ? 則在3> 執(zhí)行完畢、2> 未執(zhí)行之前,被線程二搶占了,這時lazy已經(jīng)是非null了(但卻沒有初始化),所以線程二會? ? ? ? ? ? ? ? 直接返回lazy,但是無法使用。
? 3. 內(nèi)存屏障參考:?https://zhuanlan.zhihu.com/p/43526907
*/
3. 懶漢式靜態(tài)內(nèi)部類方式實現(xiàn)單列
// 性能優(yōu)于雙重檢查的懶漢模式
// 使用內(nèi)部類可以避免多線程環(huán)境下不安全的問題,
// JVM對一個類的初始化會做限制,
// 同一個時間只會允許一個線程去初始化一個類,
// 這樣從JVM層面避免了大部分單例實現(xiàn)的問題
public class LazyInnerClassSingleton{
? ?// 默認(rèn)使用LazyInnerClassSingleton,先初始化內(nèi)部類
? ?// 如果沒使用的話,內(nèi)部類是不加載的
? ?// 為什么在私有的構(gòu)造函數(shù)中加判空判斷呢,是為了防止通過反射方式來創(chuàng)建實例
? ?// 強(qiáng)制用指定的靜態(tài)方法來實例化實例
? ?private?LazyInnerClassSingleton(){
? ? ? if (LazyHolder.LAZY != null) {
? ? ? ? ?throw new RuntimeException("不允許創(chuàng)建多個實例!")
? ? ? }
? ?}
??
? // 每一個關(guān)鍵字都不是多余的
? // static是為了使單例的空間共享
? // 保證這個方法不會被重寫,重載
? public static?LazyInnerClassSingleton getInstance() {
? ? // 在返回結(jié)果之前,一定會先加載內(nèi)部類。
? ? ?return?LazyHolder.LAZY;
? }
? // 默認(rèn)不加載??
? private static class?LazyHolder{
? ? ?private static final?LazyInnerClassSingleton LAZY = new?LazyInnerClassSingleton();
? }
}
/**
靜態(tài)內(nèi)部類實現(xiàn)單例,靜態(tài)內(nèi)部類不被調(diào)用時,默認(rèn)是不會加載的。LazyInnerClassSingleton加載時,并不需要立即加載LazyHolder,內(nèi)部類不被加載則不會去初始化,故不占用內(nèi)存。只有當(dāng)LazyInnerClassSingleton第一次被加載時,?且調(diào)用getInstance()方法時,調(diào)用了內(nèi)部類,此時內(nèi)部類去加載,這種方法不僅能確保線程安全,也能保證單例的唯一性,同時也延遲了單例的實例化。無論多少個線程去創(chuàng)建,都只會返回一個實例,返回的地址都是同一個。
*/
4. 枚舉單例??
? ?public enum EnumSingleton{
? ? ? INSTANCE;
? ? ? private Object data;
? ? ? public Object getData() {return data;}
? ? ? public static EnumSingleton getInstance(){return INSTANCE;}
? ?}
《Effective Java》Josh Bloch推薦使用此方法實例單例,線程安全,并發(fā)好,抵御反射攻擊,序列化和反序列化安全。
5. 容器式單例
// Spring中bean的獲取也是通過這種方式
public class ContainerSingleton{
? ? ?private?ContainerSingleton(){}
? ? ?private static Map<String, Object> ioc = new ConcurrentHashMap<>();
? ? ?public static Object getInstance(String className){
? ? ? ? ? synchronized(ioc) {
? ? ? ? ? ? ?if (!ioc.containsKey(className)) {
? ? ? ? ? ? ? ? Object obj = null;? ? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? try{
? ? ? ? ? ? ? ? ? ?obj = Class.forName(className).newInstance();
? ? ? ? ? ? ? ? ? ?ioc.put(className, obj);
? ? ? ? ? ? ? ? ?}catch(Exception e) {
? ? ? ? ? ? ? ? ? ? ?e.printStackTrance();
? ? ? ? ? ? ? ? ?}? ? ? ? ?
? ? ? ? ? ? }?
? ? ? ? ? ?return ioc.get(className);? ??
? ? ? ? }
? ? ?}
6. 序列化單例
? ? public class SeriableSingleton implements Seria{
? ? ? ? ?private static final?SeriableSingleton INATANCE = new?SeriableSingleton();
? ? ? ? ?private?SeriableSingleton(){}
? ? ? ? ?public static?SeriableSingleton getInstance(){
? ? ? ? ? ? ?return?INATANCE;
? ? ? ? ?}
? ? ? ? ?private Object readResolve(){return INATANCE;}
? ? }
/**
序列化就是把內(nèi)存中的狀態(tài)通過轉(zhuǎn)成字節(jié)碼的形式,從而轉(zhuǎn)換一個IO流,寫入到其他地方(可以是磁盤、網(wǎng)絡(luò)IO)
內(nèi)存中狀態(tài)給永久保存下來了。
反序列化就是將已經(jīng)持久化的字節(jié)碼內(nèi)容、轉(zhuǎn)換為IO流,通過IO流的讀取,進(jìn)而將讀取的內(nèi)容轉(zhuǎn)換為Java對象
在轉(zhuǎn)換過程中會重新創(chuàng)建對象new。
為什么要加readResolve()這個方法呢? 參考:https://blog.csdn.net/u014653197/article/details/78114041
*/
7. ThreadLocalSingleton單例
public class ThreadLocalSingleton{
? ? ?private static final ThradLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>(){
? ? ? ? ?@Override
? ? ? ? ?protected ThreadLocalSingleton initialValue() {
? ? ? ? ? ? ?return new?ThreadLocalSingleton();
? ? ? ? ?}
? ? ?}
? ? public static?ThreadLocalSingleton getInstance() {return threadLocalInstance.get()}
}
// 此種單例常用于數(shù)據(jù)庫連接池中,線程之間是隔離。
總結(jié):單例模式的特點:
? ? ? ? ? ? 1. 私有化構(gòu)造器
? ? ? ? ? ? ? ?為什么要私有化呢,如果構(gòu)造器是public修飾,那完全可以通過new來實例化該類的對象。這樣其他處的代碼就無法通? ? ? ? ? ? ? ? ? 過調(diào)用該類的構(gòu)造函數(shù)來實例化該類的對象,只有通過該類提供的靜態(tài)方法來得到該類的唯一實例。
? ? ? ? ? ?2. 保證線程安全
? ? ? ? ? ? ? ? 在多線程情況下,保證原子性。像餓漢式,枚舉單例,雙重檢查,內(nèi)部類式單例等都是線程安全的。
? ? ? ? ? ? 3. 延遲加載
? ? ? ? ? ? ? ?保證沒有被使用之前是不會加載內(nèi)存中的,
? ? ? ? ? ? 4. 防止序列化和反序列化破壞單列
? ? ? ? ? ? 5. 防御反射攻擊單列
那么class的生命周期一般來說會經(jīng)歷加載、連接、初始化、使用、和卸載五個階段:參考:?http://www.itdecent.cn/p/9f369a17d1fb
單例設(shè)計模式是23種設(shè)計模式常見的模式,也是我們熟知的模式。
此文章也參考了:?
? ? ? ? ? ? ? ? ? ? ? ? ?https://juejin.im/post/5b50b0dd6fb9a04f932ff53f
? ? ? ? ? ? ? ? ? ? ? ? ?https://cloud.tencent.com/developer/article/1177048
? ? ? ? ? ? ? ? ? ? ? ? ?http://www.itdecent.cn/p/9f369a17d1fb
文章中還有幾處知識點還沒有具體整理出來。
? ? ? ? ?