java - 如何创建 JVM 全局单例?

标签 java jvm singleton

我的灵感来自 this stackoverflow question

如何创建一个保证在整个 JVM 进程中只能使用一次的 Java 类实例?然后,在该 JVM 上运行的每个应用程序都应该能够使用该单例实例。

最佳答案

事实上,您可以实现这样的单例。在评论中向您描述的问题是一个类可能被多个 ClassLoader 加载。 s。每一个 ClassLoader s 然后可以定义一个名称相同的类,该类会错误地认为是唯一的。

但是,您可以通过对单例实现访问器来避免这种情况,该访问器明确依赖于检查特定的 ClassLoader对于一个给定名称的类,它再次包含您的单例。这样,您可以避免由两个不同的 ClassLoader 提供单例实例。 s 等复制您需要在整个 JVM 中唯一的实例。

出于后面解释的原因,我们将拆分SingletonSingletonAccessor分成两个不同的类。对于下面的类,我们稍后需要确保我们总是使用特定的 ClassLoader 来访问它。 :

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

一个方便ClassLoader对于这个问题是系统类加载器。系统类加载器知道 JVM 类路径上的所有类,并根据定义将扩展和引导类加载器作为其父类。这两个类加载器通常不知道任何特定于域的类,例如我们的 Singleton类(class)。这使我们免于意外的意外。此外,我们知道它可以在 JVM 的整个运行实例中全局访问和已知。

现在,让我们假设 Singleton类(class)在类(class)路径上。这样,我们可以使用反射通过此访问器接收实例:
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类本身并使未使用的实例腐烂以防其他 Singleton类永远加载。

然而有一个很大的缺点。你错过了所有类型安全的方法,因为你不能假设你的代码总是从 ClassLoader 运行。它委托(delegate) Singleton 的类加载到系统类加载器。对于在应用程序服务器上运行的应用程序来说尤其如此,该应用程序服务器通常为其类加载器实现子级优先语义并执行 不是 向系统类加载器询问已知类型,但首先尝试加载它自己的类型。请注意,运行时类型具有两个特征:
  • 其完全限定名称
  • 它的 ClassLoader

  • 为此,SingletonAccessor::get方法需要返回 Object而不是 Singleton .

    另一个缺点是 Singleton必须在类路径上找到类型才能使其工作。否则,系统类加载器不知道这种类型。如果你能把Singleton在类路径上输入,到这里就完成了。没问题。

    如果你不能做到这一点,还有另一种方法,例如使用我的 code generation library Byte Buddy .使用这个库,我们可以在运行时简单地定义这样一个类型并将其注入(inject)到系统类加载器中:
    new ByteBuddy()
      .subclass(Object.class)
      .name("pkg.Singleton")
      .defineField("instance", Object.class, Ownership.STATIC)
      .make()
      .load(ClassLoader.getSytemClassLoader(), 
            ClassLoadingStrategy.Default.INJECTION)
    

    您刚刚定义了一个类 pkg.Singleton对于系统类加载器,上述策略再次适用。

    此外,您可以通过实现包装器类型来避免类型安全问题。您还可以在 Byte Buddy 的帮助下自动执行此操作:
    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();
    

    您刚刚创建了一个委托(delegate)器,它覆盖了 Singleton 的所有方法。类并将它们的调用委托(delegate)给 JVM 全局单例实例的调用。请注意,我们需要重新加载反射方法,即使它们签名相同,因为我们不能依赖 ClassLoader委托(delegate)和 JVM 全局类的 s 是相同的。

    实际上,您可能希望缓存对 SingletonAccessor.get() 的调用。甚至可能是反射方法查找(与反射方法调用相比,这相当昂贵)。但是这种需求在很大程度上取决于您的应用程序域。如果您的构造函数层次结构有问题,您还可以将方法签名分解为一个接口(interface),并为上述访问器和您的 Singleton 实现此接口(interface)。类(class)。

    关于java - 如何创建 JVM 全局单例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23445434/

    相关文章:

    java - 从Midi键盘录制音频文件并将其存储在android studio中

    java - Tomcat 线程数只会增加

    java - 有没有可能的方法来查看有关 VisualVM 中方法的更详细信息?

    java - 具有延迟加载功能的单元素枚举类型单调

    iphone - 单例不会从应用程序委托(delegate)持久化到另一个 View Controller 中

    c++ - SLB中绑定(bind)单例

    java - 是否可以在android中建立多个蓝牙通信

    java - 过滤 servlet 导致图像不显示在客户端上

    java - 如何在procfile中配置系统属性?

    c++ - 这个单例类会在销毁时自动释放内存吗?