這篇文章主要講解了“java單例模式實現的方法是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“java單例模式實現的方法是什么”吧!
許多人都了解,單例的創建大致可以分為兩種:
飽漢型
餓漢型
所謂餓漢型,就是單例的對象,我上來就先創建好了,什么時候用直接拿就好了。例如這樣的方式:
private static final Singleton singleton = new Singleton();public static Singleton getInstance() {
return singleton;
}
這樣的實現方式沒有任何問題。問題出在哪呢?
有些時候,要實現Singleton的對象比較大,或者創建比較耗資源,耗時等,我們希望能在需要的時候再初始化,而不放在class 加載的時候,也就是實現所謂的lazy load。
這個時候問題就來了,這種所謂的飽漢型要怎么寫呢?
你可能見到過這種形式的實現
private static Singleton singleton;
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
這樣的實現,有問題么?是不是感覺棒棒的?
在單線程的環境中跑的話,這樣也是可以保證只返回一個instance的。
問題出在多線程環境下執行。
多個線程執行時,極有可能兩個線程同時執行到判斷是否為null的情況,又同時創建了實例出來。為了解決多線程的問題,你毫不猶豫的給方法加上了synchronized,兵不血刃的解決了問題。
問題又來了!
當這個方法使用很頻繁的時候,synchronized帶來的互斥效果,導致每次只能一個線程執行,效率很低。
此時,一個聰明的想法浮現在腦海(當然,可能是查資料,網上瀏覽了解到的)。使用雙重鎖檢查(Double lock checking)來提高效率,實現起來是這個樣子:
public static Single getInstance() {
if(singleton == null) {
synchronized(this) {
if(singleton == null) {
singleton = new Singletoon();
}
}
}
return singleton;
}
我們的方法并不是互斥的,只有在instance為空時才會加鎖檢查??此茻o懈可擊!
這個時候有一個問題,是看似普通的new XXX這種操作,其本質上和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); //普通的實例化一個對象
}
}
大致包含的步驟有:
創建對象
初始化對象的各個域,為其賦值
將對象指向其引用
但是,對于這些指令的執行,卻并不一定是按照這個順序執行,為了執行效率,這些指令會被優化,指令被重新排序。極有可能對象被創建后即指向了其引用,但各個域并沒有初始化,如果此時被使用,那拿到的就是一個構造不完整的對象。(可以參考Java并發編程實戰了解對象逃逸)
為了使代碼不被優化影響,Java 5在修訂了Java內存模型(JMM)之后,可以使用volatile聲明,不允許指令重排序。
volatile關鍵字同時保證了內存的有序性和可見性,保證程序可以按照預期執行。所以,要實現一個正確無誤的DCL單例,需要同時把singleton對象聲明為
volatile,這一定很重要。
如果不使用DCL,我們還有其它方式實現延遲初始化。例如下面這種內部類的形式,也是比較常用的。
public class Foo {
private static class FooHolder {
static final Foo foo = new Foo();
}
public static Foo getFoo() {
return FooHolder.foo;
}
}
由于內部類只有在使用時才會初始化,所以保證了單例的延遲初始化。
了解了以上這些后,我們來看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;
}
上面的代碼是關于jsp對應的Servlet獲取時對應的代碼,其中對于DCL的使用主要用于判斷jsp文件對應的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啟動才會觸發其初始化,并不會涉及到多線程環境的使用,所以可以放心使用這種方式。
看到這里的朋友,其實單例還有一種實現方式,是Effective Java的作者推薦使用的,使用起來更簡單,只需要一個枚舉項的enum即可,之后可以包含其對應的各個方法:
public enum Singleton {
INSTANCE;
public void test() {
System.out.println("test");
}
}
而我們使用的時候,直接這樣使用即可:
Singleton.INSTANCE.test();
感謝各位的閱讀,以上就是“java單例模式實現的方法是什么”的內容了,經過本文的學習后,相信大家對java單例模式實現的方法是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。