單例模式
所謂單例設(shè)計(jì)模式,就是采用一定的方法宇葱,保證在整個(gè)的軟件系統(tǒng)中,對(duì)某個(gè)類只能存在一個(gè)對(duì)象實(shí)例刊头,并且該類只提供一個(gè)取得其對(duì)象實(shí)例的方法(靜態(tài)方法)黍瞧。
1、具體實(shí)現(xiàn)步驟
- 構(gòu)造器私有化(防止外部new這個(gè)實(shí)例)
- 類的內(nèi)部創(chuàng)建對(duì)象
- 向外暴露一個(gè)靜態(tài)的公共方法
- 代碼實(shí)現(xiàn)
2原杂、單例模式注意事項(xiàng)和細(xì)節(jié)說(shuō)明
- 單例模式保證了系統(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工廠 等)
3验庙、單例模式方案合集
3.1 餓漢式(靜態(tài)常量)
3.1.1 餓漢式(靜態(tài)常量)代碼實(shí)現(xiàn)
class person{
//構(gòu)造器私有化化,外部不能new
private person(){}
//本類內(nèi)部創(chuàng)建對(duì)象實(shí)例
private final static person onePerson = new person();
//提供一個(gè)公有的靜態(tài)方法社牲,返回實(shí)例對(duì)象
public static person getPerson(){
return person;
}
}
3.1.2 優(yōu)缺點(diǎn)
- 這種寫法比較簡(jiǎn)單粪薛,就是在類裝載的時(shí)候就完成實(shí)例化。避免了線程同步問(wèn)題搏恤。
- 在類裝載的時(shí)候就完成實(shí)例化违寿,沒(méi)有達(dá)到Lazy Loading(懶加載)的效果,如果從始至終未用過(guò)這個(gè)實(shí)例熟空,則會(huì)造成內(nèi)存的浪費(fèi)藤巢。
- 這種方式基于類加載機(jī)制避免了多線程的問(wèn)題,沒(méi)有達(dá)到Lazy Loading(懶加載)的效果痛阻【瘢可用腮敌,但有可能造成內(nèi)存的浪費(fèi)阱当。
3.2 懶漢式(靜態(tài)代碼塊)
3.2.1 懶漢式(靜態(tài)代碼塊)代碼實(shí)現(xiàn)
class person{
//構(gòu)造器私有化化俏扩,外部不能new
private person(){}
//本類內(nèi)部創(chuàng)建對(duì)象實(shí)例
private static person onePerson;
//在靜態(tài)代碼塊中實(shí)例化這個(gè)對(duì)象
static{
onePerson = new person();
}
//提供一個(gè)公有的靜態(tài)方法,返回實(shí)例對(duì)象
public static person getPerson(){
return person;
}
}
3.2.2 優(yōu)缺點(diǎn)
優(yōu)缺點(diǎn)同上(餓漢式靜態(tài)常量)弊添;
3.3 懶漢式(線程不安全)
3.3.1 懶漢式(線程不安全)代碼實(shí)現(xiàn)
class person{
//本類內(nèi)部創(chuàng)建對(duì)象實(shí)例
private static person onePerson;
//構(gòu)造器私有化化录淡,外部不能new
private person(){}
//提供一個(gè)公有的靜態(tài)方法,返回實(shí)例對(duì)象
public static person getPerson(){
if(onePerson == null){
onePerson = new person();
}
return person;
}
}
3.3.2 優(yōu)缺點(diǎn)
- 起到了Lazy Loading(懶加載)的效果油坝,但是只能在單線程下使用嫉戚。
- 如果在多線程下,一個(gè)線程進(jìn)入了if (onePerson == null) 判斷語(yǔ)句塊澈圈,還未來(lái)得及往下執(zhí)行彬檀,另一個(gè)線程也通過(guò)了這個(gè)判斷語(yǔ)句,這時(shí)便會(huì)產(chǎn)生多個(gè)實(shí)例瞬女。所以在多線程環(huán)境下不可使用這種方式
- 結(jié)論:在實(shí)際開(kāi)發(fā)中窍帝,不要使用這種方式。
3.4 懶漢式(線程安全)
3.4.1 懶漢式(線程安全)代碼實(shí)現(xiàn)
class person{
//本類內(nèi)部創(chuàng)建靜態(tài)對(duì)象實(shí)例
private static person onePerson;
//構(gòu)造器私有化化诽偷,外部不能new
private person(){}
//提供一個(gè)公有的靜態(tài)方法坤学,返回實(shí)例對(duì)象
//加入同步處理的代碼,解決線程安全的問(wèn)題
public static synchronized person getPerson(){
if(onePerson == null){
onePerson = new person();
} return person;
}
}
3.4.2 優(yōu)缺點(diǎn)
- 解決了線程不安全問(wèn)題
- 效率太低了报慕,每個(gè)線程在想獲得類的實(shí)例時(shí)候深浮,執(zhí)行g(shù)etInstance()方法都要進(jìn)行同步。而其實(shí)這個(gè)方法只執(zhí)行一次實(shí)例化代碼就夠了眠冈,后面的想獲得該類實(shí)例飞苇,直接return就行了。方法進(jìn)行同步效率太低
- 結(jié)論:在實(shí)際開(kāi)發(fā)中蜗顽,不推薦使用這種方式
3.5 雙重檢查單例模式
3.5.1 雙重檢查單例模式代碼實(shí)現(xiàn)
class person{
//本類內(nèi)部創(chuàng)建靜態(tài)對(duì)象實(shí)例
private static volatile person onePerson;
//私有化構(gòu)造方法玄柠,外部不能new
private person() {}
//提供一個(gè)公有的靜態(tài)方法,返回實(shí)例對(duì)象
public static person getPerson() {
//第一次檢查诫舅,判斷是否已經(jīng)實(shí)例化
if (onePerson == null) {
//如果沒(méi)有實(shí)例化羽利,則為這個(gè)對(duì)象實(shí)例化
synchronized (person.class) {
//第二次檢查,判斷是否被其他線程給實(shí)例化了
if (onePerson == null) {
//如果沒(méi)有被實(shí)例化刊懈,說(shuō)明自己是第一個(gè)進(jìn)入此方法的線程这弧,并給其實(shí)例化。
onePerson = new person();
}
}
}
//返回實(shí)例對(duì)象
return onePerson;
}
}
3.5.2 優(yōu)缺點(diǎn)
- Double-Check概念 是多線程開(kāi)發(fā)中常使用到的虚汛,如代碼中所示匾浪,我們進(jìn)行了兩次 if (singleton == null)檢查,這樣就可以保證線程安全了卷哩。
- 這樣蛋辈,實(shí)例化代碼只用執(zhí)行一次,后面再次訪問(wèn)時(shí),判斷if (singleton == null)冷溶,直接return實(shí)例化對(duì)象渐白,也避免的反復(fù)進(jìn)行方法同步。
- 線程安全逞频;延遲加載纯衍;效率較高;
- 結(jié)論:在實(shí)際開(kāi)發(fā)中苗胀,推薦使用這種單例設(shè)計(jì)模式襟诸。
3.6 靜態(tài)內(nèi)部類單例模式
3.6.1 靜態(tài)內(nèi)部類單例模式代碼實(shí)現(xiàn)
class person{
//構(gòu)造方法私有化
private person(){}
//靜態(tài)內(nèi)部類,其中有著靜態(tài)屬性
private static class personInstance {
private static final person onePersonInstance = new person();
}
//靜態(tài)公有方法
public static person getPerson() {
return personInstance.onePersonInstance;
}
}
3.6.2 優(yōu)缺點(diǎn)
- 這種方式采用了類裝載的機(jī)制來(lái)保證初始化實(shí)例時(shí)只有一個(gè)線程基协。
- 靜態(tài)內(nèi)部類方式在 person 類被裝載時(shí)并不會(huì)立即實(shí)例化歌亲,而是在需要實(shí)例化時(shí),調(diào)用getPerson() 方法澜驮, 才會(huì)裝載 personInstance 類应结,從而完成 onePersonInstance 的實(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é)論:推薦使用。
3.7 枚舉單例模式
3.7.1 枚舉單例模式代碼實(shí)現(xiàn)
enum person {
//屬性
onePerson;
//方法
public void sayHello() {
System.out.println("Hello!");
}
}
public class PersonTest {
public static void main(String[] args) {
person personOne = person.onePerson;
person personTwo = person.onePerson;
System.out.println(personOne == personTwo);
System.out.println(personOne.hashCode());
System.out.println(personTwo.hashCode());
personOne.sayHello();
}
}
3.7.2 優(yōu)缺點(diǎn)
- 這借助JDK1.5中添加的枚舉來(lái)實(shí)現(xiàn)單例模式劲藐。不僅能避免多線程同步問(wèn)題八堡,而且還能防止反序列化重新創(chuàng)建新的對(duì)象。
- 這種方式是Effective Java作者Josh Bloch提倡的方式聘芜。
- 結(jié)論:推薦使用兄渺。
3.8 反射創(chuàng)建單例模式(新補(bǔ)充)
仔細(xì)思考下,我們是不是可以采用工廠方法模式實(shí)現(xiàn)單例模式的功能呢汰现?單例模式的核心要求就是在內(nèi)存中只有一個(gè)對(duì)象挂谍,通過(guò)工廠方法模式也可以只在內(nèi)存中生產(chǎn)一個(gè)對(duì)象,代碼如下所示瞎饲。
//單例類
public class Singleton {
//不允許通過(guò)new產(chǎn)生一個(gè)對(duì)象
private Singleton() {}
public void doSomething() {
//業(yè)務(wù)處理
}
}
Singleton保證不能通過(guò)正常的渠道建立一個(gè)對(duì)象口叙,那 SingletonFactory 如何建立一個(gè)單例對(duì)象呢?答案是通過(guò)反射方式創(chuàng)建嗅战,如代碼清單下所示妄田。
//負(fù)責(zé)生成單例的工廠類
pub1ic class SingletonFactory {
private static Singleton singleton;
static{
try {
Class cl = Class.forName (Singleton.class.getName());
//獲得無(wú)參構(gòu)造
Constructor constructor = cl.getDeclaredConstructor();
//設(shè)置無(wú)參構(gòu)造是可訪問(wèn)的
constructor.setAccessible(true);
//產(chǎn)生一個(gè)實(shí)例對(duì)象
singleton = (Sing1eton) constructor.newInstance();
} catch (Exception e) {
//異常處理
}
}
public static Singleton getSingleton() {
return singleton;
}
}
通過(guò)獲得類構(gòu)造器,然后設(shè)置訪問(wèn)權(quán)限,生成一個(gè)對(duì)象疟呐,然后提供外部訪問(wèn)脚曾,保證內(nèi)存中的對(duì)象唯一。當(dāng)然萨醒,其他類也可以通過(guò)反射的方式建立一個(gè)單例對(duì)象斟珊,確實(shí)如此苇倡,但是一個(gè)項(xiàng)目或團(tuán)隊(duì)是有章程和規(guī)范的富纸,何況已經(jīng)提供了一個(gè)獲得單例對(duì)象的方法,為什么還要重新創(chuàng)建一個(gè)新對(duì)象呢旨椒?除非是有人作惡晓褪。
以上通過(guò)工廠方法模式創(chuàng)建了一個(gè)單例對(duì)象,該框架可以繼續(xù)擴(kuò)展综慎,在一個(gè)項(xiàng)目中可以產(chǎn)生一個(gè)單例構(gòu)造器涣仿,所有需要產(chǎn)生單例的類都遵循一定的規(guī)則(構(gòu)造方法是private),然后通過(guò)擴(kuò)展該框架示惊,只要輸入一個(gè)類型就可以獲得唯一的一個(gè)實(shí)例好港。