草庐IT

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

coder 2024-03-06 原文

我的灵感来自 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 - 如何创建 JVM 全局单例?的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    3. ruby - 如何在 Ruby 中顺序创建 PI - 2

      出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

    4. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    5. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

      给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

    6. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    7. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

      我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

    8. ruby - 如何指定 Rack 处理程序 - 2

      Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

    9. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

      使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

    10. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

      在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

    随机推荐