如何在java中创建真正的单例?

如何在java中创建真正的单例?,java,singleton,absolute,Java,Singleton,Absolute,当在多个类装入器中使用我的单例时,我面临一个问题。例如,由多个EJB访问的单例。有没有办法创建一个在所有类装入器中只有一个实例的单例 我正在寻找纯java解决方案,可以使用自定义类加载器,也可以使用其他方法。唯一的方法是让您的单例类由单个类加载器加载,例如,将jar文件放在bootclasspath中 静态变量固有地绑定到类加载器,该类加载器加载了包含该变量的类。这就是它的工作方式。如果您绝对需要一个实例,那么您只需要一个类加载器来加载该类。JavaEE应用服务器通常通过将单例设置为“服务”来解

当在多个类装入器中使用我的单例时,我面临一个问题。例如,由多个EJB访问的单例。有没有办法创建一个在所有类装入器中只有一个实例的单例


我正在寻找纯java解决方案,可以使用自定义类加载器,也可以使用其他方法。

唯一的方法是让您的单例类由单个类加载器加载,例如,将jar文件放在bootclasspath中


静态变量固有地绑定到类加载器,该类加载器加载了包含该变量的类。这就是它的工作方式。如果您绝对需要一个实例,那么您只需要一个类加载器来加载该类。

JavaEE应用服务器通常通过将单例设置为“服务”来解决此问题,服务的确切定义和配置取决于所讨论的应用服务器


例如,在JBoss中,您可以使用xyz-service.xml描述符设置挂起JNDI或JMX树的单例对象,您的应用程序组件(例如EJB)将从树中获取单例。这在某种程度上保护了您不受底层类加载器语义的影响。

J2EE在设计时考虑了集群,因此它支持的任何设计都必须与多个JVM协同工作。我从您的问题中了解到,您并不关心集群环境,因此在应用服务器上简单地插入JNDI就可以了。在玻璃鱼中,这被称为a。甚至在启动之后,将您的单例插入到JNDI中,然后让其他所有东西都进行JNDI查找以找到它

请注意,GlassFish仍然可能会让您陷入困境,因为它可能会将类序列化到JNDI,从而导致您获得不同的实例。我怀疑它在一个JVM中是否真的能做到这一点,但在您尝试之前,您不会知道


真正的底线答案是J2EE不利于全局真正的单例,J2EE解决问题的方法是重新思考解决方案。J2EE方式类似于保存值的数据库或其他外部服务,可以确保只有一个数据实例(即使代表数据的对象有多个实例)存在。

要实现真正的
单例
,您必须遵循以下准则:

  • 将您的课程设置为
    final
    。其他人无法对其进行子类化并再创建一个实例
  • 将您的单例实例设置为
    private static final
  • 提供
    私有构造函数
    公共getInstance()
    方法
  • 确保此
    单例
    类仅由
    一个类加载器加载
  • 重写
    readResolve()
    方法并返回相同的实例,而不在反序列化过程中创建新实例
  • 示例代码:

    final class  LazySingleton {
        private LazySingleton() {}
        public static LazySingleton getInstance() {
            return LazyHolder.INSTANCE;
        }
        private static class LazyHolder {
            private static final LazySingleton INSTANCE = new LazySingleton();
        }
        private Object readResolve()  {
            return LazyHolder.INSTANCE;
        }
    }
    
    有关更多详细信息,请参阅以下问题:


    也许这会有帮助:你使用的是什么应用服务器?你的博客是自己写的吗?你的个人资料就是这么说的。所以“我发现了一个链接”实际上是“我写了那篇文章”。。。尽管如此,这仍然是一个有效的问题,不管问这个问题的动机是什么。我只是想证明我不能根据某人的文章“绝对单身”来创建一个绝对单身。如果这就是问题所在,我可以删除链接。我们使用Glassfish应用程序服务器和OpenESB作为Enterprise服务总线。这个单例是公共库的一部分,它在多个绑定组件和EJB之间共享。好的,请了解如何在Glassfish中创建JMX服务,并从您的组件连接到该服务。我在Google上搜索过,但除了Glassfish V3中支持EJB3.1的单例功能外,找不到任何其他内容。但我们将EJB3与Glassfish 2.1GA结合使用。我们将Glassfish应用程序服务器和OpenESB用作企业服务总线。这个单例(JCS缓存的包装器,用于存储Ldap搜索结果)是公共库的一部分,该库在多个绑定组件和EJB之间共享。@rjoshi:单例是否加载到多个类加载器中,您能避免吗?如果这个类总是在多个类加载器中,我看不出你如何避免它不是一个“合适的”单例。同时也要确保你在序列化/反序列化时很小心。最佳做法可能是避免让您的单例可序列化。@Pascal:接受答案是错误的。所以我删除了它。我使用这个单例来存储ldap缓存。我想知道其他开发人员是如何解决这个问题的?rjoshi,我想作为一个单独的问题问一些技术细节,但在我看来,你不需要它真正的全球性。把它放在JNDI中,如果一个给定的查找发生两次,就这样做。@Yishai:thx。谢谢你的建议。我会试试看,然后告诉你。我试图避免使用JNDI/EJB包装器,并使用classloader hack解决问题,但我想这是唯一的方法。。