單例模式(Singleton)
介紹
- 所謂類的單例設(shè)計(jì)模式,就是采取一定的方法,
保證在整個(gè)的軟件系統(tǒng)中,對(duì)某個(gè)類只能存在一個(gè)對(duì)象實(shí)例
,并且該類只提供一個(gè)取得其對(duì)象實(shí)例的方法(靜態(tài)方法)。 - 比如Hibernate的SessionFactory,它充當(dāng)數(shù)據(jù)存儲(chǔ)源的代理,并負(fù)責(zé)創(chuàng)建Session對(duì)象。SessionFactory并不是輕量級(jí)的,一般情況下,一個(gè)項(xiàng)目通常只需要一個(gè)SessionFactory就夠,這是就會(huì)使用到單例模式。
- 單例模式保證了系統(tǒng)內(nèi)存中該類只存在一個(gè)對(duì)象,節(jié)省了系統(tǒng)資源,
對(duì)于一些需要頻繁創(chuàng)建銷毀的對(duì)象,使用單例模式可以提高系統(tǒng)性能
- 當(dāng)想實(shí)例化一個(gè)單例類的時(shí)候,必須要記住使用相應(yīng)的獲取對(duì)象的方法,而不是使用new
- 單例模式使用的場(chǎng)景:
需要頻繁的進(jìn)行創(chuàng)建和銷毀的對(duì)象
、創(chuàng)建對(duì)象時(shí)耗時(shí)過(guò)多或耗費(fèi)資源過(guò)多(即:重量級(jí)對(duì)象),但又經(jīng)常用到的對(duì)象
、工具類對(duì)象
、頻繁訪問(wèn)數(shù)據(jù)庫(kù)或文件的對(duì)象(比如數(shù)據(jù)源、session工廠等)
單例模式的八種寫法
- 餓漢式(靜態(tài)常量)
- 餓漢式(靜態(tài)代碼塊)
- 懶漢式(線程不安全)
- 懶漢式(線程安全,同步方法)
- 懶漢式(線程安全,同步代碼塊)
- 雙重檢查
- 靜態(tài)內(nèi)部類
- 枚舉
餓漢式(靜態(tài)常量)
步驟
- 構(gòu)造器私有化(防止new)
- 類的內(nèi)部創(chuàng)建對(duì)象
- 向外暴露一個(gè)靜態(tài)的公共方法
- 代碼實(shí)現(xiàn)
實(shí)現(xiàn)
package com.atguigu.singleton.type1;
public class SingletonTest01 {
public static void main(String[] args) {
//測(cè)試
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
//餓漢式(靜態(tài)變量)
class Singleton {
//1. 構(gòu)造器私有化, 外部不能new
private Singleton() {
}
//2.本類內(nèi)部創(chuàng)建對(duì)象實(shí)例
private final static Singleton instance = new Singleton();
//3. 提供一個(gè)公有的靜態(tài)方法,返回實(shí)例對(duì)象
public static Singleton getInstance() {
return instance;
}
}
【運(yùn)行結(jié)果】
true
instance.hashCode=1554874502
instance2.hashCode=1554874502
Process finished with exit code 0
【分析】
- 優(yōu)點(diǎn):這種寫法比較簡(jiǎn)單,就是在類裝載的時(shí)候就完成實(shí)例化。避免了線程同步問(wèn)題。
- 缺點(diǎn):在類裝載的時(shí)候就完成實(shí)例化,沒(méi)有達(dá)到Lazy Loading的效果。如果從始至終從未使用過(guò)這個(gè)實(shí)例,則會(huì)造成內(nèi)存的浪費(fèi)
-
這種方式基于classloader機(jī)制避免了多線程的同步問(wèn)題
,不過(guò),instance在類裝載時(shí)就實(shí)例化,在單例模式中大多數(shù)都是調(diào)用get lnstance
方法, 但是導(dǎo)致類裝載的原因有很多種,因此不能確定有其他的方式(或者其他的靜態(tài)方法) 導(dǎo)致類裝載,這時(shí)候初始化instance就沒(méi)有達(dá)到lazy loading的效果 - 結(jié)論:
這種單例模式可用,可能造成內(nèi)存浪費(fèi)
餓漢式(靜態(tài)代碼塊)
package com.atguigu.singleton.type2;
public class SingletonTest02 {
public static void main(String[] args) {
//測(cè)試
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
//餓漢式(靜態(tài)變量)
class Singleton {
//1. 構(gòu)造器私有化, 外部能new
private Singleton() {
}
//2.本類內(nèi)部創(chuàng)建對(duì)象實(shí)例
private static Singleton instance;
static { // 在靜態(tài)代碼塊中,創(chuàng)建單例對(duì)象
instance = new Singleton();
}
//3. 提供一個(gè)公有的靜態(tài)方法,返回實(shí)例對(duì)象
public static Singleton getInstance() {
return instance;
}
}
static { // 在靜態(tài)代碼塊中,創(chuàng)建單例對(duì)象
instance = new Singleton();
}
【分析】
- 這種方式和上面的方式其實(shí)類似,只不過(guò)將類實(shí)例化的過(guò)程放在了靜態(tài)代碼塊中,也是在類裝載的時(shí)候,就執(zhí)行靜態(tài)代碼塊中的代碼,初始化類的實(shí)例。優(yōu)缺點(diǎn)和上面是一樣的
- 結(jié)論:這種單例模式可用,但是可能造成內(nèi)存浪費(fèi)
懶漢式(線程不安全)
package com.atguigu.singleton.type3;
public class SingletonTest03 {
public static void main(String[] args) {
System.out.println("懶漢式1 , 線程不安全~");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
class Singleton {
private static Singleton instance;
private Singleton() {}
//提供一個(gè)靜態(tài)的公有方法,當(dāng)使用到該方法時(shí),才去創(chuàng)建 instance
//即懶漢式
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
【分析】
- 起到了Lazy Loading的效果,但是只能在單線程下使用
- 如果在多線程下,一個(gè)線程進(jìn)入了if (singleton == null)判斷語(yǔ)句塊,還未來(lái)得及往下執(zhí)行,另一個(gè)線程也通過(guò)了這個(gè)判斷語(yǔ)句,這時(shí)便會(huì)產(chǎn)生多個(gè)實(shí)例。所以在多線程環(huán)境下不可使用這種方式
- 結(jié)論:在實(shí)際開(kāi)發(fā)中,不要使用這種方式
懶漢式(線程安全,同步方法)
package com.atguigu.singleton.type4;
public class SingletonTest04 {
public static void main(String[] args) {
System.out.println("懶漢式2 , 線程安全~");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
// 懶漢式(線程安全,同步方法)
class Singleton {
private static Singleton instance;
private Singleton() {
}
//提供一個(gè)靜態(tài)的公有方法,加入同步處理的代碼,解決線程安全問(wèn)題
//即懶漢式
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
添加synchronized
關(guān)鍵字,這樣子當(dāng)一個(gè)線程在執(zhí)行這個(gè)方法的時(shí)候,其他線程不能執(zhí)行這個(gè)方法
【分析】
- 解決了線程不安全問(wèn)題
- 效率太低了,每個(gè)線程在想獲得類的實(shí)例時(shí)候,執(zhí)行g(shù)etlnstance()方法都要進(jìn)行同步。而其實(shí)這個(gè)方法只執(zhí)行一次實(shí)例化代碼就夠了,后面的想獲得該類實(shí)例,直接return就行了。方法進(jìn)行同步效率太低
- 結(jié)論:在實(shí)際開(kāi)發(fā)中,不推薦使用這種方式
懶漢式(線程安全,同步代碼塊)
class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class){
instance = new Singleton();
}
}
return instance;
}
}
【分析】
- 這種方式,本意是想對(duì)第四種實(shí)現(xiàn)方式的改進(jìn),因?yàn)榍懊嫱椒椒ㄐ侍透臑橥疆a(chǎn)生實(shí)例化的的代碼塊
- 但是這種同步并不能起到線程同步的作用。跟第3種實(shí)現(xiàn)方式遇到的情形一致,假如一個(gè)線程進(jìn)入了if(singleton == nul)判斷語(yǔ)句塊,還未來(lái)得及往下執(zhí)行另一個(gè)線程也通過(guò)了這個(gè)判斷語(yǔ)句,這時(shí)便會(huì)產(chǎn)生多個(gè)實(shí)例
- 結(jié)論:在實(shí)際開(kāi)發(fā)中,不能使用這種方式
雙重檢查(推薦使用)
package com.atguigu.singleton.type6;
public class SingletonTest06 {
public static void main(String[] args) {
System.out.println("雙重檢查");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
class Singleton {
//volatile:在Java中,volatile關(guān)鍵字可以保證變量的內(nèi)存可見(jiàn)性。
//當(dāng)一個(gè)變量被聲明為volatile時(shí),編譯器和處理器會(huì)注意到這個(gè)變量可能會(huì)被其他線程并發(fā)地訪問(wèn)。
//這樣可以避免線程之間的數(shù)據(jù)競(jìng)爭(zhēng),并確保多線程環(huán)境下變量的值是最新的。
private static volatile Singleton instance;
private Singleton() {
}
//提供一個(gè)靜態(tài)的公有方法,加入雙重檢查代碼,解決線程安全問(wèn)題, 同時(shí)解決懶加載問(wèn)題
//同時(shí)保證了效率, 推薦使用
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
保證只有一個(gè)線程創(chuàng)建實(shí)例
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
【分析】
- Double-Check概念是多線程開(kāi)發(fā)中常使用到的,如代碼中所示,我們進(jìn)行了兩次if (singleton == null)檢查,這樣就可以保證線程安全了
- 這樣,實(shí)例化代碼只用執(zhí)行一次,后面再次訪問(wèn)時(shí),判斷if (singleton == null)直接return實(shí)例化對(duì)象,也避免的反復(fù)進(jìn)行方法同步
- 線程安全;延遲加載;效率較高
- 在實(shí)際開(kāi)發(fā)中,推薦使用這種單例設(shè)計(jì)模式
靜態(tài)內(nèi)部類(推薦使用)
package com.atguigu.singleton.type7;
public class SingletonTest07 {
public static void main(String[] args) {
System.out.println("使用靜態(tài)內(nèi)部類完成單例模式");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
// 靜態(tài)內(nèi)部類完成, 推薦使用
class Singleton {
//構(gòu)造器私有化
private Singleton() {}
//寫一個(gè)靜態(tài)內(nèi)部類,該類中有一個(gè)靜態(tài)屬性 Singleton
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
//提供一個(gè)靜態(tài)的公有方法,直接返回SingletonInstance.INSTANCE
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
【分析】
- 當(dāng)加載類的時(shí)候,靜態(tài)內(nèi)部類是不會(huì)被加載的,調(diào)用getInstance()時(shí),靜態(tài)內(nèi)部類才會(huì)被裝載,而且只會(huì)裝載一次
- 這種方式采用了類裝載的機(jī)制來(lái)保證初始化實(shí)例時(shí)只有一個(gè)線程
- 靜態(tài)內(nèi)部類方式在Singleton類被裝載時(shí)并不會(huì)立即實(shí)例化,而是在需要實(shí)例化時(shí),調(diào)用getInstance方法,才會(huì)裝載Singletonlnstance類,從而完成Singleton的實(shí)例化
- 類的靜態(tài)屬性只會(huì)在第一次加載類的時(shí)候初始化,所以在這里,JVM幫助我們保證了線程的安全性,在類進(jìn)行初始化時(shí),別的線程是無(wú)法進(jìn)入的
- 優(yōu)點(diǎn):
避免了線程不安全,利用靜態(tài)內(nèi)部類特點(diǎn)實(shí)現(xiàn)延遲加載,效率高
- 結(jié)論:推薦使用
枚舉(推薦使用,而且很方便)
package com.atguigu.singleton.type8;
public class SingletonTest08 {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance == instance2);
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());
instance.sayOK();
}
}
//使用枚舉,可以實(shí)現(xiàn)單例, 推薦
enum Singleton {
INSTANCE; //屬性
public void sayOK() {
System.out.println("ok~");
}
}
【運(yùn)行】
true
1554874502
1554874502
ok~
【分析】
- 這借助JDK1.5中添加的枚舉來(lái)實(shí)現(xiàn)單例模式。不僅能避免多線程同步問(wèn)題,而且還能防止反序列化重新創(chuàng)建新的對(duì)象
- 這種方式是Effective Java作者Josh Bloch 提倡的方式
單例模式在JDK中的應(yīng)用
聯(lián)系
案例:3例模式
【題目】
請(qǐng)編寫Triple類,實(shí)現(xiàn)最多只能生成3個(gè)Triple類的實(shí)例,實(shí)例編號(hào)分別為0,1,2且可以通過(guò)getInstance(int id)來(lái)獲取該編號(hào)對(duì)應(yīng)的實(shí)例(題目來(lái)源于《圖解設(shè)計(jì)模式》)
【代碼實(shí)現(xiàn)】
public class Triple {
private static volatile Triple[] arr = new Triple[3];
private Triple() {
}
public static Triple getInstance(int index) {
if (index >= arr.length) {
throw new RuntimeException("索引超出,示例數(shù)量只有" + arr.length + "個(gè)");
}
if (arr[index] == null) {
synchronized (Triple.class) {
if (arr[index] == null) {
arr[index] = new Triple();
}
}
}
return arr[index];
}
}
public class TripleMain {
public static void main(String[] args) {
System.out.println("Start.");
for (int i = 0; i < 9; i++) {
Triple triple = Triple.getInstance(i % 3);
System.out.println(i + ":" + triple);
}
System.out.println("End.");
}
}
【運(yùn)行】文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-549314.html
Start.
0:com.atguigu.singleton.A2.Triple@5cad8086
1:com.atguigu.singleton.A2.Triple@6e0be858
2:com.atguigu.singleton.A2.Triple@61bbe9ba
3:com.atguigu.singleton.A2.Triple@5cad8086
4:com.atguigu.singleton.A2.Triple@6e0be858
5:com.atguigu.singleton.A2.Triple@61bbe9ba
6:com.atguigu.singleton.A2.Triple@5cad8086
7:com.atguigu.singleton.A2.Triple@6e0be858
8:com.atguigu.singleton.A2.Triple@61bbe9ba
End.
文章說(shuō)明
本文章為本人學(xué)習(xí)尚硅谷的學(xué)習(xí)筆記,文章中大部分內(nèi)容來(lái)源于尚硅谷視頻(點(diǎn)擊學(xué)習(xí)尚硅谷相關(guān)課程),也有部分內(nèi)容來(lái)自于自己的思考,發(fā)布文章是想幫助其他學(xué)習(xí)的人更方便地整理自己的筆記或者直接通過(guò)文章學(xué)習(xí)相關(guān)知識(shí),如有侵權(quán)請(qǐng)聯(lián)系刪除,最后對(duì)尚硅谷的優(yōu)質(zhì)課程表示感謝。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-549314.html
到了這里,關(guān)于【設(shè)計(jì)模式】23種設(shè)計(jì)模式——單例模式(原理講解+應(yīng)用場(chǎng)景介紹+案例介紹+Java代碼實(shí)現(xiàn))的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!