設計模式(三)——JDK中的那些單例

喝水會長肉發表於2021-12-12

在 中介紹了單例的概念、用途、實現方式、如何防止被序列化破壞等。單例模式在JDK原始碼中也有多處應用。本文通過JDK(java 8)中幾個典型的單例的使用來複習一下單例模式,並且通過這種實際應用來深入理解一下單例的用法與實現方式。

java.lang.Runtime

Runtime類封裝了Java執行時的環境。每一個java程式實際上都是啟動了一個JVM程式,那麼每個JVM程式都是對應這一個Runtime例項,此例項是由JVM為其例項化的。每個 Java 應用程式都有一個 Runtime 類例項,使應用程式能夠與其執行的環境相連線。

由於Java是單程式的,所以,在一個JVM中,Runtime的例項應該只有一個。所以應該使用單例來實現。


public 
class 
Runtime 
{

    private static Runtime currentRuntime = new Runtime ( ) ;

    public static Runtime getRuntime ( ) {
        return currentRuntime ;
    }

    private Runtime ( ) { }
}

以上程式碼為JDK中 Runtime類的部分實現,可以看到,這其實是餓漢式單例模式。在該類第一次被classloader載入的時候,這個例項就被建立出來了。

一般不能例項化一個 Runtime物件,應用程式也不能建立自己的 Runtime 類例項,但可以通過  getRuntime 方法獲取當前 Runtime執行時物件的引用。

GUI中的單例

除了 Runtime是典型的單例以外。JDK中還有幾個類是單例的,他們都是GUI中的類。這幾個單例的類和 Runtime最大的區別就在於他們並不是餓漢模式,也就是他們都是惰性初始化的懶漢單例。如果分析其原因的話也比較簡單:那就是他們並不需要事先建立好,只要在第一次真正用到的時候再建立就可以了。因為很多時候我們並不是用Java的GUI和其中的物件。如果使用餓漢單例的話會影響JVM的啟動速度。

由於Java的強項並不是做GUI,所以這幾個類其實並不會經常被用到。筆者也沒用過。把程式碼貼到這裡,從單例的實現的角度簡單分析一下。

java.awt.Toolkit#getDefaultToolkit()


public abstract 
class 
Toolkit 
{

    /**
    * The default toolkit.
    */

    private static Toolkit toolkit ;

    public static synchronized Toolkit getDefaultToolkit ( ) {
            if (toolkit == null ) {
               java .security .AccessController . doPrivileged (
                        new java .security .PrivilegedAction <Void > ( ) {
                    public Void run ( ) {
                       Class < ? > cls = null ;
                       String nm = System . getProperty ( "awt.toolkit" ) ;
                        try {
                           cls = Class . forName (nm ) ;
                        } catch (ClassNotFoundException e ) {
                           ClassLoader cl = ClassLoader . getSystemClassLoader ( ) ;
                            if (cl != null ) {
                                try {
                                   cls = cl . loadClass (nm ) ;
                                } catch (final ClassNotFoundException ignored ) {
                                    throw new AWTError ( "Toolkit not found: " + nm ) ;
                                }
                            }
                        }
                        try {
                            if (cls != null ) {
                               toolkit = (Toolkit )cls . newInstance ( ) ;
                                if (GraphicsEnvironment . isHeadless ( ) ) {
                                   toolkit = new HeadlessToolkit (toolkit ) ;
                                }
                            }
                        } catch (final InstantiationException ignored ) {
                            throw new AWTError ( "Could not instantiate Toolkit: " + nm ) ;
                        } catch (final IllegalAccessException ignored ) {
                            throw new AWTError ( "Could not access Toolkit: " + nm ) ;
                        }
                        return null ;
                    }
                } ) ;
                loadAssistiveTechnologies ( ) ;
            }
            return toolkit ;
        }
    }

上面的程式碼是 Toolkit類的單例實現。這裡類載入時只靜態宣告瞭私有 toolkit並沒有建立 Toolkit例項物件,延遲載入加快了JVM啟動速度。

單例模式作為一種建立模式,這裡在依賴載入的時候應用了另一種建立物件的方式,不是 new新的物件,因為 Toolkit本身是個抽象類不能例項化物件,而是通過反射機制載入類並建立新的例項。

java.awt.GraphicsEnvironment#getLocalGraphicsEnvironment()


public abstract 
class 
GraphicsEnvironment 
{

    private static GraphicsEnvironment localEnv ;
    public static synchronized GraphicsEnvironment getLocalGraphicsEnvironment ( ) {
        if (localEnv == null ) {
           localEnv = createGE ( ) ;
        }

        return localEnv ;
    }
}

這裡類載入時只靜態宣告瞭私有 localEnv並沒有建立例項物件。在 GraphicsEnvironment類被第一次呼叫時會建立該物件。這裡沒有貼出的 createGE()方法也是通過 反射的方式建立物件的。

java.awt.Desktop#getDesktop()


public 
class 
Desktop 
{


    public static synchronized Desktop getDesktop ( ) {
        if (GraphicsEnvironment . isHeadless ( ) ) throw new HeadlessException ( ) ;
        if ( !Desktop . isDesktopSupported ( ) ) {
            throw new UnsupportedOperationException ( "Desktop API is not " +
                                                    "supported on the current platform" ) ;
        }
//java學習交流:737251827  進入可領取學習資源及對十年開發經驗大佬提問,免費解答!
       sun .awt .AppContext context = sun .awt .AppContext . getAppContext ( ) ;
       Desktop desktop = (Desktop )context . get (Desktop .class ) ;

        if (desktop == null ) {
           desktop = new Desktop ( ) ;
           context . put (Desktop .class , desktop ) ;
        }

        return desktop ;
    }
}

上面的程式碼看上去和單例不太一樣。但是實際上也是執行緒安全的懶漢式單例。獲取物件的時候先去環境容器中查詢是否存在,不存在例項則建立一個例項。

以上三個類的獲取例項的方法都通過同步方法的方式保證了執行緒安全。

Runtime類是通過靜態初始化的方式保證其執行緒安全的。

總結

文中介紹了四個單例的例子,其中有一個是餓漢式單例,三個是懶漢式單例。通過JDK中的實際應用我們可以得出以下結論:

當一個類的物件只需要或者只可能有一個時,應該考慮單例模式。

如果一個類的例項應該在JVM初始化時被建立出來,應該考慮使用餓漢式單例。

如果一個類的例項不需要預先被建立,也許這個類的例項並不一定能用得上,也許這個類的例項建立過程比較耗費時間,也許就是真的沒必須提前建立。那麼應該考慮懶漢式單例。

在使用懶漢式單例的時候,應該考慮到執行緒的安全性問題。



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70010294/viewspace-2847255/,如需轉載,請註明出處,否則將追究法律責任。

相關文章