2014-05-03 51 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(),甚至可能会反射方法查找(与反射方法调用相比,这些查找相当昂贵)。但是这种需求很大程度上取决于你的应用领域如果您在构造函数层次结构中遇到问题,还可以将方法签名分解为一个接口,并为上述访问器和您的类实现此接口。

相关问题