一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - Java教程 - Java 設(shè)計模式實戰(zhàn)系列—單例模式

Java 設(shè)計模式實戰(zhàn)系列—單例模式

2023-09-24 03:15未知服務(wù)器之家 Java教程

本文首發(fā)公眾號:小碼A夢 單例模式是設(shè)計模式中最簡單一個設(shè)計模式,該模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建實例的最佳方式。 單例模式的定義也比較簡單:一個類只能允許創(chuàng)建一個對象或者實例,那么這個類就是單例類,

本文首發(fā)公眾號:小碼A夢

單例模式是設(shè)計模式中最簡單一個設(shè)計模式,該模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建實例的最佳方式。

單例模式的定義也比較簡單:一個類只能允許創(chuàng)建一個對象或者實例,那么這個類就是單例類,這種設(shè)計模式就叫做單例模式。

單例模式有哪些好處:

  • 類的創(chuàng)建,特別是一個大型的類,只創(chuàng)建一個類,避免內(nèi)存和 CPU 的開銷。
  • 降低內(nèi)存使用,減少 GC 次數(shù),避免 GC 的壓力。
  • 避免對資源的重復(fù)請求。
  • 避免創(chuàng)建多個實例引起系統(tǒng)的混亂或者系統(tǒng)數(shù)據(jù)沖突。

ID 生成器單例類實戰(zhàn)

單例模式,其中的 "例" 表示 "實例" ,一個類需要保證僅有一個實例,并提供一個訪問它的全局訪問點,實現(xiàn)一個單例,需要符合以下幾點要求:

  • 構(gòu)造函數(shù)需要設(shè)置 private 權(quán)限,避免外部通過 new 創(chuàng)建實例,通過一個靜態(tài)方法給其他類獲取實例。
  • 對象創(chuàng)建需要考慮線程安全問題。
  • 需要考慮延遲加載問題。
  • 外部類獲取實例需要考慮性能方法。

在電商系統(tǒng)的訂單模塊,每次下單都需要生成新的訂單號。就需要調(diào)用訂單號生成器。

Java 設(shè)計模式實戰(zhàn)系列—單例模式

1、 創(chuàng)建一個簡單單例類

public class SnGenerator {

    private AtomicLong id = new AtomicLong(0);
    // 創(chuàng)建一個 Singleton 對象
    private static SnGenerator instance = new SnGenerator();

    // 構(gòu)造函數(shù)設(shè)置為 private,類就無法被實例化
    private SnGenerator() {}

    // 獲取唯一實例
    public static SnGenerator getInstance() {
        return instance;
    }

    public long getSn() {
        return id.incrementAndGet();
    }
}

2、獲取 Singleton 類的唯一實例

public class SingletonTest {

    public static void main(String[] args) {
        // 編譯報錯,因為 Singleton 構(gòu)造函數(shù)是私有的
        //Singleton singleton = new Singleton();

        SnGenerator snGenerator = SnGenerator.getInstance();
        for (int i = 0; i < 10; i++) {
            System.out.println(snGenerator.getSn());
        }

    }
}

控制臺輸出生成的 id:

1
2
3
4
5
6
7
8
9
10

以上首先創(chuàng)建一個單例類,提供唯一的單例獲取方法 getInstance。SingletonTest 類通過 Singleton.getInstance 獲取實例,獲取到實例,也就獲取到實例所有的方法。示例中調(diào)用 getSn 方法,獲取到唯一的訂單號了。

Java 設(shè)計模式實戰(zhàn)系列—單例模式

餓漢單例

餓漢單例實現(xiàn)起來比較簡單,所謂 "餓漢" 重點在餓,開始就需要創(chuàng)建單例。在類加載時,就創(chuàng)建好了實例。所以 instance 實例的創(chuàng)建是線程安全,不存在線程安全問題。但是這種方式不支持延遲加載,類加載時占用的內(nèi)存就比較高。

public class SnGenerator {

    private AtomicLong id = new AtomicLong(0);

    private static SnGenerator instance = new SnGenerator();

    private SnGenerator() {}

    public static SnGenerator getInstance() {
        return instance;
    }

    public long getSn() {
        return id.incrementAndGet();
    }
}

餓漢單例解決線程安全問題,項目啟動時就創(chuàng)建好了實例,就需要考慮創(chuàng)建和獲取實例的線程安全問題。但是不支持延遲,如果實例的占用內(nèi)存比較大,或者實例加載時間比較長,類加載的時候就創(chuàng)建實例,就比較浪費內(nèi)存或者增加項目啟動時間。

對餓漢單例來說,不支持延遲加載,確實是比較浪費內(nèi)存。但是一個實例內(nèi)存相對于一個 Java 項目內(nèi)存占用影響是微乎其微。部署服務(wù)端項目時會分配幾倍于項目啟動占用的內(nèi)存,所以餓漢單例占用內(nèi)存還是可以接受的。而且如果占用內(nèi)存比較大,初始化實例也可以發(fā)現(xiàn)內(nèi)存不足的問題,并及時的處理。避免程序運行后,再去初始化實例,導(dǎo)致系統(tǒng)內(nèi)存溢出,影響系統(tǒng)穩(wěn)定性。

懶漢單例

既然餓漢單例單例不支持延遲加載,那我們就介紹一下支持延遲的加載的單例:懶漢單例。所謂"懶漢"重點在懶,一開始是不會初始化實例,而等到被調(diào)用才會初始化單例

public class LazySnGenerator {

    private AtomicLong id = new AtomicLong(0);

    private static LazySnGenerator instance;

    // 構(gòu)造函數(shù)設(shè)置為 private,類就無法被實例化
    private LazySnGenerator() {}

    // 獲取唯一實例
    public static LazySnGenerator getInstance() {
        if (instance == null) {
            instance = new LazySnGenerator();
        }
        return instance;
    }

    public long getSn() {
        return id.incrementAndGet();
    }

}

上面的懶漢單例最開始不會初始化實例,而且等到 getInstance 方法被調(diào)用時,才會時候?qū)嵗@樣支持懶加載的方式,優(yōu)點是不占內(nèi)存。

但是懶漢單例缺點也比較明顯,在多線程環(huán)境下,getInstance 方法不是線程安全的。

打個比方,多個線程同時執(zhí)行到 if (instance == null)結(jié)果都為 true,進而都會創(chuàng)建實例,所以上面的懶漢單例不是線程安全的實例。

加同步鎖的懶漢單例

懶漢單例存在多線程安全問題,第一想到的就是給 getInstance 添加同步鎖,添加鎖后,保證了線程的安全。


public class LazySnGenerator {

    private AtomicLong id = new AtomicLong(0);

    private static LazySnGenerator instance;

    // 構(gòu)造函數(shù)設(shè)置為 private,類就無法被實例化
    private LazySnGenerator() {}

    // 獲取唯一實例
    public synchronized static LazySnGenerator getInstance() {
        if (instance == null) {
            instance = new LazySnGenerator();
        }
        return instance;
    }

    public long getSn() {
        return id.incrementAndGet();
    }

}

添加同步鎖后懶漢單例,并發(fā)量下降,如果方法被頻繁使用,頻繁的加鎖、釋放鎖,有很大的性能瓶頸。

雙重檢驗懶漢單例

餓漢單例不支持延遲加載,懶漢單例有性能問題,不支持高并發(fā)。就需要一種既支持延遲加載又支持高并發(fā)的單例,也就是雙重檢驗懶漢單例。對上面的懶漢單例進行優(yōu)化之后,得出如下代碼。

public class LazyDoubleCheckSnGenerator {

    private AtomicLong id = new AtomicLong(0);

    private static LazyDoubleCheckSnGenerator instance;

    // 構(gòu)造函數(shù)設(shè)置為 private,類就無法被實例化
    private LazyDoubleCheckSnGenerator() {}

    // 雙重檢測
    public static LazyDoubleCheckSnGenerator getInstance() {
        if (instance == null) {
            // 類級別鎖
            synchronized (LazyDoubleCheckSnGenerator.class) {
                if (instance == null) {
                    instance = new LazyDoubleCheckSnGenerator();
                }
            }
        }
        return instance;
    }

    public long getSn() {
        return id.incrementAndGet();
    }
}

雙重檢測首先判斷實例是否為空,如果為空就使用類級別鎖鎖住整個類,其他線程也只能等待實例新建后,才能執(zhí)行 synchronized 代碼塊的代碼,而此時 instance 不為空,就不會繼續(xù)新建實例。從而確保線程安全。getInstance 只會在最開始的時候,性能較差。創(chuàng)建實例之后,后面的線程都不會請求到 synchronized 代碼塊。后續(xù)并發(fā)性能也提高了。

CPU 指令重排可能會導(dǎo)致新建對象并賦值給 instance 之后,還來得及初始化,就會其他線程使用。導(dǎo)致系統(tǒng)報錯,為了解決這個問題,就需要給 instance 成員變量添加 volatile 關(guān)鍵字禁止指令重排。

靜態(tài)內(nèi)部類單例

和雙重檢測單例一樣,靜態(tài)內(nèi)部類既支持延遲加載又支持高并發(fā)。首先看一下代碼實現(xiàn)。

public class SnStaticClass {
    private AtomicLong id = new AtomicLong(0);

    private static LazySnGenerator instance;

    // 構(gòu)造函數(shù)設(shè)置為 private,類就無法被實例化
    private SnStaticClass() {}

    private static class SingletonHolder{
        private static final SnStaticClass instance = new SnStaticClass();
    }

    // 靜態(tài)內(nèi)部類獲取實例
    public synchronized static SnStaticClass getInstance() {
        return SingletonHolder.instance;
    }

    public long getSn() {
        return id.incrementAndGet();
    }
}

SingletonHolder 是一個靜態(tài)內(nèi)部類,當 SnStaticClass 加載時,并不會加載 SingletonHolder 靜態(tài)內(nèi)部類,也就不會執(zhí)行靜態(tài)內(nèi)部的代碼。在類加載初始化階段,不會執(zhí)行靜態(tài)內(nèi)部類的代碼。只有 getInstance 方法,執(zhí)行 SingletonHolder 靜態(tài)內(nèi)部類,才會創(chuàng)建 SnStaticClass 實例。而 instance 創(chuàng)建的安全性,都是由 JVM 保證的。虛擬機使用加鎖同步機制,保證實例只會創(chuàng)建一次。這種方式不僅實現(xiàn)延遲加載,也保證線程安全

枚舉單例

枚舉實例單例是一個簡答實現(xiàn)方式,這種方式是通過 Java 枚舉類性本身的特性,來保證實例的唯一和線程的安全。

public enum SnGeneratorEnum {

    instance;

    private AtomicLong id = new AtomicLong(0);

    public long getSn() {
        return id.incrementAndGet();
    }

}

單例模式的應(yīng)用

在 Java 開發(fā)中,有很多地方使用到了單例模式。比如 JDK、Spring。

JDK

Runtime 類封裝了 Java 運行信息,可以獲取有關(guān)運行時環(huán)境的信息,每個 JVM 進程只有一個運行環(huán)境,只需要一個 Runtime 實例,所以 Runtime 一個單例實現(xiàn)。

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

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}
    
    ......省略
}

由以上代碼可知,Runtime 是一個餓漢單例,類加載時就初始化了實例,提供 getRuntime 方法提交單例的調(diào)用。

Spring

大部分 Java 項目都是基于 Spring 框架開發(fā)的,Spring 中 bean 簡單分成單例和多例,其中 bean 的單例實現(xiàn)既不是餓漢單例也不是懶漢單例。是基于單例注冊表實現(xiàn),注冊表就就是一個哈希表,使用一個哈希表存儲 bean 的信息。

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

singletonObjects 表示一個單例注冊表,key 存儲 bean 的名稱,value 存儲 bean 的實例信息。DefaultSingletonBeanRegistry 類的 getSingleton 方法實現(xiàn) bean 單例,以下摘取主要的的代碼。

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
        // 鎖住注冊表
		synchronized (this.singletonObjects) {
            // 獲取 bean 信息,不存在就創(chuàng)建一個 bean
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
				
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				
				try {
                    // 創(chuàng)建 bean
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				catch (IllegalStateException ex) {
					
				}
				catch (BeanCreationException ex) {
					
				}
				finally {
					
				}
                // 創(chuàng)建好的 bean 存進 map 中
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

Spring 獲取 bean,鎖住整個注冊表,首先從 map 中獲取 bean,如果 bean 不存在,就創(chuàng)建一個 bean,并存入 map 中。后續(xù)獲取 bean,獲取到的都是 map 的 bean。并不會創(chuàng)建新的 bean。

總結(jié)

單例模式一個最簡單的一種設(shè)計模式,該設(shè)計模式是一種創(chuàng)建型設(shè)計模式。規(guī)定了一個類只能創(chuàng)建一實例。很多類只需要一個實例,這樣的好處,減少內(nèi)存的占用和 CPU 的開銷,減少 GC 的次數(shù)。同時也減少對資源的重復(fù)使用。

  • 以生成訂單系統(tǒng)的訂單號為例,分別介紹幾種單例模式。
    • 餓漢單例:線程安全,但不支持延遲加載,不使用也會占用內(nèi)存,比較浪費內(nèi)存。但是類加載時創(chuàng)建實例,可以及時的發(fā)現(xiàn)內(nèi)存不足問題。
    • 懶漢單例:支持延遲加載,但是線程不安全。多線程獲取實例,可能會創(chuàng)建多個實例,就需要使用同步鎖,鎖住獲取實例的方法,但是加了鎖之后,性能就比較差。
    • 雙重檢測懶漢單例:針對上面不同同時滿足延遲加載和線程安全問題,就設(shè)計出來雙重檢測的懶漢單例,主要將鎖的代碼塊范圍縮小,先獲取實例,如果實例為空,才使用類級別鎖,鎖住代碼,創(chuàng)建實例。當創(chuàng)建好實例后,后面請求都不會進同步鎖的代碼塊,性能也不會降低。還需要考慮指令重排的問題,需要給成員變量添加 volatile 關(guān)鍵字禁止指令重排。
    • 靜態(tài)內(nèi)部類:也同時滿足延遲加載和線程安全,延遲加載是在類加載時不會靜態(tài)內(nèi)部類的代碼,只有調(diào)用時候才會執(zhí)行靜態(tài)內(nèi)部類的代碼。JVM 使用同步鎖的機制保證獲取實例是線程安全的。
    • 枚舉單例:通過 Java 枚舉類性本身的特性,來保證實例的唯一和線程的安全。
  • 單例模式的應(yīng)用
    • JDK: Runtime 封裝了 Java 運行信息,可以獲取有關(guān)運行時環(huán)境的信息,一個 JVM 只需要一個 Runtime 實例.Runtime 單例是餓漢單例,在類加載時就初始化實例。
    • Spring: Spring 的 bean 支持單例,使用單例注冊表,一種哈希表存儲 bean信息,key 是存儲 bean 的名稱,value 是存儲 bean 的實例。獲取 bean 首先鎖住表,然后獲取 bean,如果為空就創(chuàng)建 bean,并存入表中,后續(xù)都能從哈希表中獲取 bean 實例了。

參考

  • 單例模式(上):為什么說支持懶加載的雙重檢測不比餓漢式更優(yōu)?

  • Spring學習之路——單例模式和多例模式

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 久久免费看少妇高潮A片特爽 | 青青青国产手机在线播放 | 无码国产成人午夜在线观看不卡 | 欧美日韩国产精品综合 | 青青在线观看视频 | 欧美多gayxxxx| caoporm国产精品视频免费 | 91po国产在线高清福利 | aaa免费看| 青草国产| 俄罗斯一级成人毛片 | 啊啊啊好大好爽视频 | 好男人资源大全免费观看 | 成人涩涩屋福利视频 | 423hk四虎 | 国产九九热视频 | 9420高清视频在线观看网百度 | 国产极品麻豆91在线 | 关晓彤一级做a爰片性色毛片 | 女人zooxx禽交 | 成人小视频在线观看 | 天堂网在线.www天堂在线视频 | 福利视频一区青娱 | 久久丫线这里只精品 | 国产区香蕉精品系列在线观看不卡 | 日本一道一区二区免费看 | 日本高清视频在线的 | 女八把屁股扒开让男生添 | 欧美日韩久久中文字幕 | 成人福利影院 | 午夜伦午夜伦锂电影 | 国产高清一区二区三区免费视频 | 免费观看美景之屋 | 欧美xbxbxbbxxbb精品 | 日本一卡2卡3卡4卡乱 | 欧美人成绝费网站色www吃脚 | 国产成人一区二区三区在线视频 | 欧美精品一区视频 | 欧美特黄三级在线观看 | 精品卡1卡2卡三卡免费视频 | 男人久久天堂 |