小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

深入靈魂的考驗,每行注釋都是靈魂的單例模式,源碼+實例降臨

 頭號碼甲 2021-06-23

不管是設(shè)計模式也好,別的模式也要,他都是為了解決問題而發(fā)明的有效的方法。除了我們已經(jīng)熟悉的23種設(shè)計模式以外,還有MVVM、Combinator等其它的東西,都已經(jīng)是前輩們經(jīng)過多年的摸爬滾打總結(jié)出來的,其有效性不容置疑。我這篇文章也不會用來證明設(shè)計模式是有用的,因為在我看來,這就跟1+1=2一樣明顯(在黑板上寫下1+1=2)

而這,在現(xiàn)在這個追求高質(zhì)量代碼的時代,雖然顯得有一些復(fù)雜,但是我個人還是“推崇”(看好了,我有引號的)這個東西,畢竟面試必問系列,你咋整

來看今天的內(nèi)容吧,有代碼,有實例,并且有一些內(nèi)容我直接放在代碼中通過注釋進行講解,會更好理解

文章首發(fā)公眾號:Java架構(gòu)師聯(lián)盟,每日更新技術(shù)好文

一、設(shè)計部分:單例的實現(xiàn)思想、代碼及注意問題
package com.test.hibernate;

/*生成一個懶漢式單例的基礎(chǔ)理解:
1.Singleton顧名思義就是只能創(chuàng)建一個實例對象。。所以不能擁有public的構(gòu)造方法
2.既然構(gòu)造方法是私有的,那么從外面不可能創(chuàng)建Singleton實例了。。只能從內(nèi)部創(chuàng)建。。所以需要一個方法來創(chuàng)建此實例,也因此只能通過類名來創(chuàng)建對象。。此方法肯定必須是static的
3.靜態(tài)的getInstance方法要返回一個Singleton實例。。就要一個Singleton類型的變量來存儲。。聲明一個Singleton類型的屬性。。同樣需要是static 的。。靜態(tài)方法只能訪問靜態(tài)屬性。。。
?。。∏?步是單例的共性!后三步是懶漢式需要考慮的地方!
4.為了保證只生成一個實例,需要做判斷是否為null
5.此時考慮線程問題,假設(shè)有兩個線程。。thread1,thread2。。thread1運行到判斷那個Singleton類型的變量是否為null,然后跳到了thread2。。也運行到判斷之后。。。此時兩線程都得到single為空。。。那么就會有兩個實例了。。。解決辦法。。同步
6.同步又要考慮效率,不能有太多的沒用同步
* */


//思考總結(jié):其實餓漢式?jīng)]什么問題,問題就是出現(xiàn)在懶漢式上,一般就是牽扯到執(zhí)行效率和線程安全2個角度上來思考
//個人感覺 如果是餓漢式就用天然的沒毛病,如果想用懶漢式就用靜態(tài)內(nèi)部類方式吧
//存在問題:如何在2個jvm上保證單例還未解決:這個就牽扯到分布式鎖,可以用zookeeper來實現(xiàn)
//還缺少一種懶漢式的枚舉方式實現(xiàn)有待研究,聽說這個方法也不錯。

public class danli { //模擬一下靜態(tài)代碼塊的使用方式,靜態(tài)代碼塊在類加載的運行,先靜態(tài)代碼塊》再構(gòu)造代碼塊》再構(gòu)造函數(shù) ,只研究單例可以忽略

public static final String STR1;
static {
STR1 = new String("zzh");
}

}
//下面正式演示各種單例的實現(xiàn):

class danli2{//單例餓漢式(非延時加載),提前加載,有利于速度和反應(yīng)時間,天然的線程安全的。沒毛病
private danli2(){};
private static final danli2 two = new danli2();//final可加可不加,final的目的就是最終的,只允許一次賦值,但不加是因為沒法在本類外給他賦值了,因為構(gòu)造方法是私有的沒法創(chuàng)建這個類的對象了,而且這個成員變量也是私有的所以不能在外面調(diào)用到,但是可以在本類中的其他方法調(diào)用到,所以其實還是可以修改的,所以還是加上final吧
public static danli2 getSingleInstance(){
return two;
}
}

 

class danli3{ //單例懶漢式(延時加載),用的時候再去加載,有利于資源充分利用
private danli3(){};
private static danli3 three = null;
public static synchronized danli3 getSingleInstance(){//加上synchronized變得線程安全了,但是效率下降了,每次還需要檢查同步等等
if(three == null){//保證只生成一個實例
three = new danli3();
}
return three;
}
}
/* 該類跟上面那個是一樣的,上面是synchronized方法,下面這個是代碼塊。
class Singleton {

private Singleton() {}

private volatile static Singleton instance = null;

public static Singleton getInstance() {

synchronized (Singleton.class) {//利用synchronized代碼塊,每次需要先檢查有沒有同步鎖,效率較低,為了解決這個問題又提出了加入雙層檢查,也就是在這個同步代碼塊的外面再加一層為null判斷,來減少除第一次以外的同步檢查,提高了效率
if (instance == null) {
instance = new Singleton();
}
}

return instance;
}
}
*/
雙重檢查加鎖就是在同步代碼塊的外面一層再來一個== null的判斷,解決除第一次以外所有的同步判斷導(dǎo)致的效率下降問題
//但是這個雙重檢查加鎖在多線程環(huán)境下存在系統(tǒng)崩潰的可能(一個線程初始化一半的對象,被第二個線程直接拿去用了,所以系統(tǒng)崩潰了)
/*原因如下
1、線程 1 進入 getInSingleton() 方法。
2、由于 uniqueInstance 為 null,線程 1 在 //1 處進入 synchronized 塊。
3、線程 1 前進到 //3 處,但在構(gòu)造函數(shù)執(zhí)行之前,使實例成為非 null。
4、線程 1 被線程 2 預(yù)占。
5、線程 2 檢查實例是否為 null。因為實例不為 null,線程 2 將 uniqueInstance 引用返回給一個構(gòu)造完整但部分初始化了的 Singleton 對象。
6、線程 2 被線程 1 預(yù)占。
7、線程 1 通過運行 Singleton 對象的構(gòu)造函數(shù)并將引用返回給它,來完成對該對象的初始化。

*/
class Singleton {//雙重檢查加鎖,線程相對安全了,避開了過多的同步(因為這里的同步只需在第一次創(chuàng)建實例時才同步,一旦創(chuàng)建成功,以后獲取實例時就不需要同獲取鎖了),效率比上面那個能提高一些
// volatile關(guān)鍵字確保當uniqueInstance變量被初始化成Singleton實例時,多個線程正確地處理uniqueInstance變量,這個關(guān)鍵字其實也解決了上面說的系統(tǒng)可能崩潰的問題,因為使用這個變量也需要一個線程一個線程的來使用了
private volatile static Singleton uniqueInstance;
private Singleton() {
}

public static Singleton getInSingleton() {
if (uniqueInstance == null) {// 檢查實例,如是不存在就進行同步代碼區(qū)
synchronized (Singleton.class) {//1 // 對其進行鎖,防止兩個線程同時進入同步代碼區(qū)
if (uniqueInstance == null) {//2 // 雙重檢查,非常重要,如果兩個同時訪問的線程,當?shù)谝痪€程訪問完同步代碼區(qū)后,生成一個實例;當?shù)诙€已進入getInstance方法等待的線程進入同步代碼區(qū)時,也會產(chǎn)生一個新的實例
uniqueInstance = new Singleton();//3
}
}
}
return uniqueInstance;
}
// ...Remainder omitted
}

//使用靜態(tài)內(nèi)部類是沒問題的,而且效率也不會降低,而且還是懶加載
class Singleton2 {//jvm加載SingletonHolder的時候會初始化INSTANCE,所以既是lazy的又保證是單例的
private static class SingletonHolder {//靜態(tài)內(nèi)部類,只會被加載一次(在加載外部類的時候),所以線程安全,注意靜態(tài)只能使用靜態(tài)
static final Singleton2 INSTANCE = new Singleton2();
}

private Singleton2 (){}//靜態(tài)構(gòu)造方法

public static Singleton2 getInstance() {//對外提供單例的接口
return SingletonHolder.INSTANCE;
}
}


class ceshi{//只是簡單測試了一下單例,都為true,可以忽略
public static void main(String[] args) {
System.out.println(danli.STR1 == danli.STR1);//true
System.out.println(danli2.getSingleInstance() == danli2.getSingleInstance());
System.out.println(danli3.getSingleInstance() == danli3.getSingleInstance());
System.out.println(Singleton.getInSingleton() == Singleton.getInSingleton());
System.out.println(Singleton2.getInstance() == Singleton2.getInstance());
}
}
二、應(yīng)用部分:單例的適用場景
優(yōu)點:
第一、能減少資源的使用,但有時需要通過線程同步來控制資源的并發(fā)訪問;也避免對共享資源的多重占用

第二、控制實例產(chǎn)生的數(shù)量(允許可變數(shù)目的實例),由于在系統(tǒng)內(nèi)存中只存在一個對象,因此可以 節(jié)約系統(tǒng)資源,當 需要頻繁創(chuàng)建和銷毀的對象時單例模式無疑可以提高系統(tǒng)的性能。

第三、作為通信媒介使用,也就是數(shù)據(jù)共享,共享這一個對象一個實例(如線程池),它可以在不建立直接關(guān)聯(lián)的條件下,讓多個不相關(guān)的兩個線程或者進程之間實現(xiàn)通信,但注意多線程同步問題。

缺點:
1.不太適用于變化的對象,如果同一類型的對象總是要在不同的用例場景發(fā)生變化,單例就會引起數(shù)據(jù)的錯誤,不能保存彼此的狀態(tài),所以就算保存了,需要加入同步機制來避免錯誤。
2.由于單例模式中沒有抽象層,因此單例類的擴展有很大的困難。
3.單例類的職責過重,在一定程度上違背了“單一職責原則”。
4.濫用單例將帶來一些負面問題,如為了節(jié)省資源將數(shù)據(jù)庫連接池對象設(shè)計為的單例類,可能會導(dǎo)致共享連接池對象的程序過多而出現(xiàn)連接池溢出;如果實例化的對象長時間不被利用,系統(tǒng)會認為是垃圾而被回收,這將導(dǎo)致對象狀態(tài)的丟失。

使用注意事項:
1.使用時不能用反射模式創(chuàng)建單例,否則會實例化一個新的對象
2.使用懶單例模式時注意線程安全問題
3.餓單例模式和懶單例模式構(gòu)造方法都是私有的,因而是不能被繼承的,有些單例模式可以被繼承(如登記式模式)

適合場景:
1、有頻繁實例化然后銷毀的情況,也就是頻繁的 new 對象,可以考慮單例模式;

2、創(chuàng)建對象時耗時過多或者耗資源過多,但又經(jīng)常用到的對象;

3、頻繁訪問 IO 資源的對象,例如數(shù)據(jù)庫連接池或訪問本地文件;

4、單例模式只允許創(chuàng)建一個對象,因此節(jié)省內(nèi)存,加快對象訪問速度,因此對象需要被公用的場合適合使用,如多個模塊使用同一個數(shù)據(jù)源連接對象等等。

具體應(yīng)用場景舉例:
外部資源:每臺計算機有若干個打印機,但只能有一個PrinterSpooler,以避免兩個打印作業(yè)同時輸出到打印機。
內(nèi)部資源:大多數(shù)軟件都有一個(或多個)屬性文件存放系統(tǒng)配置,這樣的系統(tǒng)應(yīng)該有一個對象管理這些屬性文件,在我們?nèi)粘J褂玫脑赪indows中也有不少單例模式設(shè)計的組件,象常用的文件管理器。由于Windows操作系統(tǒng)是一個典型的多進程多線程系統(tǒng),那么在創(chuàng)建或者刪除某個文件的時候,就不可避免地出現(xiàn)多個進程或線程同時操作一個文件的現(xiàn)象。采用單例模式設(shè)計的文件管理器就可以完美的解決這個問題,所有的文件操作都必須通過唯一的實例進行,這樣就不會產(chǎn)生混亂的現(xiàn)象。
Windows的Task Manager(任務(wù)管理器)就是很典型的單例模式(這個很熟悉吧),想想看,是不是呢,你能打開兩個windows task manager嗎? 不信你自己試試看哦~
windows的Recycle Bin(回收站)也是典型的單例應(yīng)用。在整個系統(tǒng)運行過程中,回收站一直維護著僅有的一個實例。
網(wǎng)站的計數(shù)器,一般也是采用單例模式實現(xiàn),否則難以同步。
應(yīng)用程序的日志應(yīng)用,一般都何用單例模式實現(xiàn),這一般是由于共享的日志文件一直處于打開狀態(tài),因為只能有一個實例去操作,否則內(nèi)容不好追加。
Web應(yīng)用的配置對象的讀取,一般也應(yīng)用單例模式,這個是由于配置文件是共享的資源。
數(shù)據(jù)庫連接池的設(shè)計一般采用單例模式,數(shù)據(jù)庫連接是一種數(shù)據(jù)庫資源。軟件系統(tǒng)中使用數(shù)據(jù)庫連接池,主要是節(jié)省打開或者關(guān)閉數(shù)據(jù)庫連接所引起的效率損耗,這種效率上的損耗還是非常昂貴的。當然,使用數(shù)據(jù)庫連接池還有很多其它的好處,可以屏蔽不同數(shù)據(jù)數(shù)據(jù)庫之間的差異,實現(xiàn)系統(tǒng)對數(shù)據(jù)庫的低度耦合,也可以被多個系統(tǒng)同時使用,具有高可復(fù)用性,還能方便對數(shù)據(jù)庫連接的管理等等。數(shù)據(jù)庫連接池屬于重量級資源,一個應(yīng)用中只需要保留一份即可,既節(jié)省了資源又方便管理。所以數(shù)據(jù)庫連接池采用單例模式進行設(shè)計會是一個非常好的選擇。
多線程的線程池的設(shè)計一般也是采用單例模式,這是由于線程池要方便對池中的線程進行控制。
spring的bean(scope)默認是single,當然也可以當然也可以設(shè)置為prototype,比如struts2的action就必須是prototype,因為請求不同,一個請求對應(yīng)一個action對象。
我們知道單例會發(fā)生線程安全問題,那么spring是怎么來解決的呢?
問題:當Bean對象對應(yīng)的類存在可變的成員變量并且其中存在改變這個變量的線程時,多線程操作該Bean對象時會出現(xiàn)線程安全。原因:當多線程中存在線程改變了bean對象的可變成員變量時,其他線程無法訪問該bean對象的初始狀態(tài),從而造成數(shù)據(jù)錯亂解決方式:1.在Bean對象中盡量避免定義可變的成員變量;2.在bean對象中定義一個ThreadLocal成員變量,將需要的可變成員變量保存在ThreadLocal中
2個具體場景案例
1、網(wǎng)站在線人數(shù)統(tǒng)計;

其實就是全局計數(shù)器,也就是說所有用戶在相同的時刻獲取到的在線人數(shù)數(shù)量都是一致的。要實現(xiàn)這個需求,計數(shù)器就要全局唯一,也就正好可以用單例模式來實現(xiàn)。當然這里不包括分布式場景,因為計數(shù)是存在內(nèi)存中的,并且還要保證線程安全。下面代碼是一個簡單的計數(shù)器實現(xiàn)。

public class Counter {

private static class CounterHolder{
private static final Counter counter = new Counter();
}

private Counter(){
System.out.println("init...");
}

public static final Counter getInstance(){
return CounterHolder.counter;
}

private AtomicLong online = new AtomicLong();

public long getOnline(){
return online.get();
}

public long add(){
return online.incrementAndGet();
}
.......
}
1、配置文件訪問類;

項目中經(jīng)常需要一些環(huán)境相關(guān)的配置文件,比如短信通知相關(guān)的、郵件相關(guān)的。比如 properties 文件,這里就以讀取一個properties 文件配置為例,如果你使用的 Spring ,可以用 @PropertySource 注解實現(xiàn),默認就是單例模式。如果不用單例的話,每次都要 new 對象,每次都要重新讀一遍配置文件,很影響性能,如果用單例模式,則只需要讀取一遍就好了。以下是文件訪問單例類簡單實現(xiàn):

public class SingleProperty {

private static Properties prop;

private static class SinglePropertyHolder{
private static final SingleProperty singleProperty = new SingleProperty();
}

/**
* config.properties 內(nèi)容是 test.name=kite
*/
private SingleProperty(){
System.out.println("構(gòu)造函數(shù)執(zhí)行");
prop = new Properties();
InputStream stream = SingleProperty.class.getClassLoader()
.getResourceAsStream("config.properties");
try {
prop.load(new InputStreamReader(stream, "utf-8"));
} catch (IOException e) {
e.printStackTrace();
}
}

public static SingleProperty getInstance(){
return SinglePropertyHolder.singleProperty;
}


public String getName(){
return prop.get("test.name").toString();
}

public static void main(String[] args){
SingleProperty singleProperty = SingleProperty.getInstance();
System.out.println(singleProperty.getName());
}
}
深入靈魂的考驗,每行注釋都是靈魂的單例模式,源碼+實例降臨

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多