溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

java單例模式實現(xiàn)的方法是什么

發(fā)布時間:2022-01-14 10:45:43 來源:億速云 閱讀:143 作者:iii 欄目:大數(shù)據(jù)

這篇文章主要講解了“java單例模式實現(xiàn)的方法是什么”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“java單例模式實現(xiàn)的方法是什么”吧!

許多人都了解,單例的創(chuàng)建大致可以分為兩種:

  • 飽漢型

  • 餓漢型

所謂餓漢型,就是單例的對象,我上來就先創(chuàng)建好了,什么時候用直接拿就好了。例如這樣的方式:

private static final Singleton singleton = new Singleton();
 
public static Singleton getInstance() {
   return singleton;
}

這樣的實現(xiàn)方式?jīng)]有任何問題。問題出在哪呢?

有些時候,要實現(xiàn)Singleton的對象比較大,或者創(chuàng)建比較耗資源,耗時等,我們希望能在需要的時候再初始化,而不放在class 加載的時候,也就是實現(xiàn)所謂的lazy load。

這個時候問題就來了,這種所謂的飽漢型要怎么寫呢?

你可能見到過這種形式的實現(xiàn)

private static Singleton singleton;
public static Singleton getInstance() {
   if (singleton == null) {
       singleton = new Singleton();
   }
   return singleton;
}

這樣的實現(xiàn),有問題么?是不是感覺棒棒的?

在單線程的環(huán)境中跑的話,這樣也是可以保證只返回一個instance的。

問題出在多線程環(huán)境下執(zhí)行。

多個線程執(zhí)行時,極有可能兩個線程同時執(zhí)行到判斷是否為null的情況,又同時創(chuàng)建了實例出來。為了解決多線程的問題,你毫不猶豫的給方法加上了synchronized,兵不血刃的解決了問題。

問題又來了!

當(dāng)這個方法使用很頻繁的時候,synchronized帶來的互斥效果,導(dǎo)致每次只能一個線程執(zhí)行,效率很低。

此時,一個聰明的想法浮現(xiàn)在腦海(當(dāng)然,可能是查資料,網(wǎng)上瀏覽了解到的)。使用雙重鎖檢查(Double lock checking)來提高效率,實現(xiàn)起來是這個樣子:

public static Single getInstance() {
   if(singleton == null) {
       synchronized(this) {
           if(singleton == null) {
               singleton = new Singletoon();
           }
       }
   }
   return singleton;
}

我們的方法并不是互斥的,只有在instance為空時才會加鎖檢查??此茻o懈可擊!

這個時候有一個問題,是看似普通的new XXX這種操作,其本質(zhì)上和i++

這種操作一樣,并不是一個原子操作。例如,我們下面這幾行代碼:

public class Test {
   private int i = 5;
   private int a = 2;
   public Test(int i, int a) {
       this.i = i;
       this.a = a;
   }
   public void ttt() {
      Test t = new Test(1,1); //普通的實例化一個對象
   }
}

大致包含的步驟有:

  1. 創(chuàng)建對象

  2. 初始化對象的各個域,為其賦值

  3. 將對象指向其引用

但是,對于這些指令的執(zhí)行,卻并不一定是按照這個順序執(zhí)行,為了執(zhí)行效率,這些指令會被優(yōu)化,指令被重新排序。極有可能對象被創(chuàng)建后即指向了其引用,但各個域并沒有初始化,如果此時被使用,那拿到的就是一個構(gòu)造不完整的對象。(可以參考Java并發(fā)編程實戰(zhàn)了解對象逃逸)

為了使代碼不被優(yōu)化影響,Java 5在修訂了Java內(nèi)存模型(JMM)之后,可以使用volatile聲明,不允許指令重排序。

volatile關(guān)鍵字同時保證了內(nèi)存的有序性可見性,保證程序可以按照預(yù)期執(zhí)行。所以,要實現(xiàn)一個正確無誤的DCL單例,需要同時把singleton對象聲明為

volatile,這一定很重要。

如果不使用DCL,我們還有其它方式實現(xiàn)延遲初始化。例如下面這種內(nèi)部類的形式,也是比較常用的。

public class Foo {
   private static class FooHolder {
       static final Foo foo = new Foo();
   }
   public static Foo getFoo() {
       return FooHolder.foo;
   }
}

由于內(nèi)部類只有在使用時才會初始化,所以保證了單例的延遲初始化。

了解了以上這些后,我們來看Tomcat中的單例,是如何使用的。

首先我們來看看Tomcat中對于DCL的使用。

/** Whether the servlet needs reloading on next access */
private volatile boolean reload = true;
 

public Servlet getServlet() throws ServletException {
   // DCL on 'reload' requires that 'reload' be volatile
   // (this also forces a read memory barrier, ensuring the
   // new servlet object is read consistently)
   if (reload) {
       synchronized (this) {
           // Synchronizing on jsw enables simultaneous loading
           // of different pages, but not the same page.
           if (reload) {
               // This is to maintain the original protocol.
               destroy();
               final Servlet servlet;
               servlet.init(config);
               reload = false;
               // Volatile 'reload' forces in order write of 'theServlet' and new servlet object
           } }  }
   return theServlet;
}

上面的代碼是關(guān)于jsp對應(yīng)的Servlet獲取時對應(yīng)的代碼,其中對于DCL的使用主要用于判斷jsp文件對應(yīng)的class是否需要重新加載。(jsp文件工作原理前面文章介紹過,感興趣的朋友可以看JSP文件修改實時生效的秘密)

單例的使用,Tomcat中的方式很簡單,

public final class ApplicationFilterFactory {

   private static ApplicationFilterFactory factory = null;

   private ApplicationFilterFactory() {
       // Prevent instantiation outside of the getInstanceMethod().
   }
 
/**
* Return the factory instance.
*/
public static ApplicationFilterFactory getInstance() {
   if (factory == null) {
       factory = new ApplicationFilterFactory();
   }
   return factory;
}

我們看到和前面第一次提到的飽漢型一樣,沒有使用DCL,也沒加

synchronized,這是因為在Tomcat中對于此處ApplicationFactory的使用,只有在StandardWrapperValve啟動才會觸發(fā)其初始化,并不會涉及到多線程環(huán)境的使用,所以可以放心使用這種方式。

看到這里的朋友,其實單例還有一種實現(xiàn)方式,是Effective Java的作者推薦使用的,使用起來更簡單,只需要一個枚舉項的enum即可,之后可以包含其對應(yīng)的各個方法:

public enum Singleton {
   INSTANCE;
   public void test() {
       System.out.println("test");
   }
}

而我們使用的時候,直接這樣使用即可:

Singleton.INSTANCE.test();

感謝各位的閱讀,以上就是“java單例模式實現(xiàn)的方法是什么”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對java單例模式實現(xiàn)的方法是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI