如何创建JVM全局Singleton? [英] How to create a JVM-global Singleton?

查看:76
本文介绍了如何创建JVM全局Singleton?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我从此stackoverflow问题

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

How can one create a Java class instance that is guaranteed to be available only once for the entire JVM process? Every Application that runs on that JVM should then be able to use that singleton instance.

推荐答案

您可以实际上实现这样的单身人士。注释中描述的问题是类可能由多个 ClassLoader 加载。然后,这些 ClassLoader 中的每一个都可以定义一个相同名称的类,它会错误地认为是唯一的。

You can as a matter of fact implement such a singleton. The problem that was described to you in the comments is the possibility of a class being loaded by multiple ClassLoaders. Each of these ClassLoaders can then define a class of identical name which would erroneously assume to be unique.

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

You can however avoid this by implementing an accessor to your singleton which explicitly relies on checking a specific ClassLoader for a class of a given name which again contains your singleton. This way, you can avoid that a singleton instance is provided by two different ClassLoaders and such duplicating the instance you required to be unique throughout the JVM.

由于后面会解释的原因,我们将拆分 Singleton SingletonAccessor 分为两个不同的班级。对于以下类,我们稍后需要确保始终使用特定的 ClassLoader 访问它:

For reasons that are explained later, we will split up the Singleton and the SingletonAccessor into two different classes. For the following class, we need to later make sure that we always access it by using a specific ClassLoader:

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

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

A convenient ClassLoader for this matter is the system class loader. The system class loader is aware of all classes on the JVM's class path and has per definition the extension and the bootstrap class loaders as its parents. Those two class loaders do not normally know about any classes that are domain-specific such as our Singleton class. This safes us from unwanted surprises. Furthermore, we know that it is accessible and known globally throughout a running instance of the JVM.

现在,让我们假设 Singleton 类在类路径上。这样,我们可以使用反射通过此访问器接收实例:

For now, let us assume that the Singleton class is on the class path. This way, we can receive the instance by this accessor using reflection:

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 类的情况下使未使用的实例生效已经加载。

By specifying that we explicitly want to load pkg.Singleton from the system class loader, we make sure that we always receive the same instance despite of which class loader loaded our SingletonAccessor. In the above example, we additionally make sure that Singleton is only instantiated once. Alternatively, you could put the instantiation logic into the Singleton class itself and make the unused instances rot in case other Singleton classes are ever loaded.

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

There is however a big drawback. You miss all means of type-safety as you cannot assume that your code is always run from a ClassLoader which delegates the class loading of Singleton to the system class loader. This is in particularly true for an application run on an application server which often implements child-first semantics for its class loaders and does not ask the system class loader for known types but first tries to load its own types. Note that a runtime type is characterized by two features:


  1. 其完全限定名称

  2. ClassLoader

  1. Its fully-qualified name
  2. Its ClassLoader

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

For this reason, the SingletonAccessor::get method needs to return Object instead of Singleton.

另一个缺点是必须在类路径上找到 Singleton 类型才能使其工作。否则,系统类加载器不知道此类型。如果你可以将 Singleton 类型放到类路径上,那么你就完成了。没有问题。

Another drawback is the fact that the Singleton type must be found on the class path for this to work. Otherwise, the system class loader does not know about this type. If you can put the Singleton type onto the class path, you are done here. No problems.

如果你不能做到这一点,那么有另一种方法可以使用例如我的代码生成库Byte Buddy 。使用这个库,我们可以在运行时简单地定义这样一个类型并将其注入系统类加载器:

If you cannot make this happen, there is however another way by using for example my code generation library Byte Buddy. Using this library, we can simply define such a type at runtime and inject it into the system class loader:

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

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

You just defined a class pkg.Singleton for the system class loader and the above strategy is applicable again.

此外,您可以通过实现包装类型来避免类型安全问题。你也可以在Byte Buddy的帮助下实现自动化:

Also, you can avoid the type-safety issues by implementing a wrapper type. You can also automatize this with the help of 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();

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

You just created a delegator which overrides all methods of the Singleton class and delegates their invocation to invocations of the JVM-global singleton instance. Note that we need to reload the reflective methods even though they are signature-identical because we cannot rely on the ClassLoaders of the delegate and the JVM-global classes to be the same.

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

In practice, you might want to cache the calls to SingletonAccessor.get() and maybe even the reflective method look-ups (which are rather expensive compared to the reflective method invocations). But this need depends highly on your application domain. If you have trouble with your constructor hierarchy, you could also factor out the method signatures into an interface and implement this interface for both the above accessor and your Singleton class.

这篇关于如何创建JVM全局Singleton?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆