如何创建JVM全局Singleton?

 林姗飘零1999 发布于 2023-01-17 10:21

我从这个stackoverflow问题中汲取灵感

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

1 个回答
  • 事实上你可以实现这样一个单身人士.在评论中向您描述的问题是多个ClassLoaders 加载类的可能性.ClassLoader然后,这些中的每一个都可以定义一个相同名称的类,它将错误地认为是唯一的.

    但是,您可以通过实现单例的访问器来避免这种情况,该访问器明确依赖于检查特定ClassLoader名称的类,该类还包含您的单例.这样,您可以避免单个实例由两个不同的ClassLoaders 提供,并且这样复制您需要在整个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类本身,并在未Singleton加载其他类的情况下使未使用的实例生效.

    然而,有一个很大的缺点.您错过了所有类型安全的方法,因为您不能假设您的代码始终从ClassLoader将类加载委托Singleton给系统类加载器的代码运行.这是特别真实的应用服务器上的应用程序运行,往往实现儿童优先语义的类加载器和并没有要求已知类型的系统类加载器,但首先会尝试加载自己的类型.请注意,运行时类型具有两个特征:

      它的完全限定名称

      它的 ClassLoader

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

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

    如果你不能实现这一点,那么有另一种方法可以使用我的代码生成库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为系统类加载器定义了一个类,上述策略再次适用.

    此外,您可以通过实现包装类型来避免类型安全问题.你也可以在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该类的所有方法,并将其调用委托给JVM全局单例实例的调用.请注意,我们需要重新加载反射方法,即使它们是签名相同的,因为我们不能依赖ClassLoader委托的s和JVM全局类是相同的.

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

    2023-01-17 10:25 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有