2014-05-03 47 views
8

我從this stackoverflow question獲得靈感如何創建一個JVM全局Singleton?

如何創建一個Java類實例,該類實例保證只對整個JVM進程可用?在該JVM上運行的每個應用程序都應該能夠使用該單例實例。

+7

由於這些應用程序使用的所有自定義類加載器都無法保證。 –

+1

如果您試圖限制對某個對象的訪問,則需要使用某種物理分區。這裏有一篇關於「什麼時候不是單身人士?」的文章。 http://www.oracle.com/technetwork/articles/java/singleton-1577166.html有很多方法可以獲得不是單身人士的單身人士。 – mttdbrd

+0

這完全取決於您的執行環境。您最好使用某種文件或OS作業屬性來存儲持久性值。 –

回答

11

你可以事實上實現這樣一個單例。在評論中描述給您的問題是多個ClassLoader加載的類的可能性。然後這些ClassLoader中的每一個都可以定義一個錯誤地假定爲唯一的相同名稱的類別。

但是,你可以通過實現一個訪問器來避免這種情況,這個訪問器明確依賴於檢查特定名稱的類別ClassLoader,該類名稱還包含你的單例。通過這種方式,您可以避免由兩個不同的ClassLoader提供單例實例,並且這樣會重複在JVM中需要唯一的實例。

由於後面解釋的原因,我們會將SingletonSingletonAccessor分成兩個不同的類別。對於下面的類,我們以後需要確保我們總是通過使用特定的ClassLoader訪問:

package pkg; 
class Singleton { 
    static volatile Singleton instance; 
} 

方便的ClassLoader這件事情是系統類加載器。系統類加載器知道JVM的類路徑上的所有類,並且每個定義都有擴展名和引導類加載器作爲其父類。這兩個類裝載機通常不知道任何特定於域的類,例如我們的Singleton類。這使我們免於意外的驚喜。此外,我們知道它在整個JVM的運行實例中都是可訪問的並且已知的。

現在讓我們假設Singleton類在類路徑上。通過這種方式,我們可以使用反射通過這個訪問收到實例:

class SingletonAccessor { 
    static Object get() { 
    Class<?> clazz = ClassLoader.getSystemClassLoader() 
           .findClass("pkg.Singleton"); 
    Field field = clazz.getDeclaredField("instance"); 
    synchronized (clazz) { 
     Object instance = field.get(null); 
     if(instance == null) { 
     instance = clazz.newInstance(); 
     field.set(null, instance); 
     } 
     return instance; 
    } 
    } 
} 

通過指定我們明確地希望從系統類加載器加載pkg.Singleton,我們確保我們總是收到相同的情況下,儘管其類裝載機加載我們的SingletonAccessor。在上面的例子中,我們另外確保Singleton僅實例化一次。或者,您可以將實例化邏輯放入Singleton類中,並在未加載類的情況下使未使用的實例腐爛。

但是有一個很大的缺點。你錯過了所有類型安全的方法,因爲你不能假設你的代碼總是從ClassLoader運行,它將類加載Singleton委託給系統類加載器。這對於在應用程序服務器上運行的應用程序尤其如此,該應用程序服務器通常爲其類加載器實現兒童優先語義,並且而不是向系統類加載器詢問已知類型,但首先嚐試加載其自己的類型。請注意,運行時類型的特點是兩個特點:

  1. 其完全合格的名稱
  2. ClassLoader

出於這個原因,SingletonAccessor::get方法需要返回Object,而不是Singleton

另一個缺點是必須在類路徑上找到Singleton類型才能使其工作。否則,系統類加載器不知道這種類型。如果您可以將Singleton類型放置到課程路徑中,則可以在此完成。沒問題。

如果你不能做到這一點,但有另一種方法,例如使用我的code generation library Byte Buddy。使用這個庫,我們可以簡單地在運行時定義這樣的類型,注入系統類加載器:

new ByteBuddy() 
    .subclass(Object.class) 
    .name("pkg.Singleton") 
    .defineField("instance", Object.class, Ownership.STATIC) 
    .make() 
    .load(ClassLoader.getSytemClassLoader(), 
     ClassLoadingStrategy.Default.INJECTION) 

你剛纔定義的類pkg.Singleton爲系統類加載器和上述策略又是適用的。

此外,您可以通過實現包裝類型來避免類型安全問題。你也可以用字節好友的幫助你完成這項:

new ByteBuddy() 
    .subclass(Singleton.class) 
    .method(any()) 
    .intercept(new Object() { 
    @RuntimeType 
    Object intercept(@Origin Method m, 
        @AllArguments Object[] args) throws Exception { 
     Object singleton = SingletonAccessor.get(); 
     return singleton.getClass() 
     .getDeclaredMethod(m.getName(), m.getParameterTypes()) 
     .invoke(singleton, args); 
    } 
    }) 
    .make() 
    .load(Singleton.class.getClassLoader(), 
     ClassLoadingStrategy.Default.INJECTION) 
    .getLoaded() 
    .newInstance(); 

您剛剛創建,其覆蓋Singleton類和代表他們調用的所有方法對JVM-全球單一實例的調用一個委託。請注意,即使它們是簽名相同的,我們也需要重新加載反射方法,因爲我們不能依賴委託的ClassLoader和JVM全局類相同。

實際上,您可能希望將調用緩存到SingletonAccessor.get(),甚至可能會反射方法查找(與反射方法調用相比,這些查找相當昂貴)。但是這種需求很大程度上取決於你的應用領域如果您在構造函數層次結構中遇到問題,還可以將方法簽名分解爲一個接口,併爲上述訪問器和您的類實現此接口。

相關問題