单例模式并不是真正的单例。

4

我有这样一种情况,我的代码运行在嵌入式服务器和Web应用程序之间共享单例。我有一个包含类和部署工具的war文件。当我使用printf方法打印实例时,我看到:

abc.Abc@173a10f
abc.Abc@105738

所以这并不是真正的单例模式。它是如何工作的?


我的Jetty服务器启动代码:

public static void main(String[] args) throws Exception
{
    System.out.println(MySingleton.getInstance());
    // start Jetty here and deploy war with WebAppContext()
}

我的 ServletContextListener 代码:

public class AppServletContextListener implements ServletContextListener{
    @Override
    public void contextInitialized(ServletContextEvent arg0) {
        System.out.println(MySingleton.getInstance());
    }
}

我的单例模式:

public class MySingleton {
    private static MySingleton INSTANCE = new MySingleton();
    private MySingleton () {}
    public static MySingleton getInstance() {
        return INSTANCE;
    }
}

我在构造函数中强制抛出异常。看起来我得到了两个不同的结果。

java.lang.Exception
        at api.MySingleton.<init>(MySingleton.java:33)
        at api.MySingleton.<clinit>(MySingleton.java:22)
        at my.project.StartJetty.main(StartJetty.java:41)
java.lang.Exception
        at api.MySingleton.<init>(MySingleton.java:33)
        at api.MySingleton.<clinit>(MySingleton.java:22)
        at api.AppServletContextListener.contextInitialized(AppServletContextListener.java:25)
        at org.eclipse.jetty.server.handler.ContextHandler.startContext(ContextHandler.java:640)
        at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:229)
        at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1208)
        at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:586)
        at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:449)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:58)
        at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:224)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:58)
        at org.eclipse.jetty.server.handler.HandlerWrapper.doStart(HandlerWrapper.java:89)
        at org.eclipse.jetty.server.Server.doStart(Server.java:258)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:58)
        at my.project.StartJetty.main(StartJetty.java:66)

2
你一点代码都没发布。 - chrylis -cautiouslyoptimistic-
1
但是服务器是否可以运行部署页面(restful 代码,servlet 等)的独立实例呢? - Knight of Ni
3
AppServletContextListenermain方法是否在同一个JVM中执行? - Sotirios Delimanolis
2
Jetty 必须使用自己的 ClassLoader 加载其自己的 MyInstance 类实例。 - Sotirios Delimanolis
2
这可能是一次无从下手的尝试...但你的MySingleton类是否被多个ClassLoader加载了?如果你的JVM中有两个不相互通信的加载器,那么同一个类的两个不同定义可能会同时存在(例如,在NetBeans RCP框架中经常发生这种情况,所以这并不是前所未有的)。 - CodeBlind
显示剩余9条评论
3个回答

5

看一下Jetty文档,你可以尝试修改类加载配置。

如果设置为true,则Jetty使用正常的JavaSE类加载优先级,并将优先考虑使用父/系统类加载器。这避免了Web应用程序中多个版本的类的问题,但是父/系统加载器提供的版本必须是您以这种方式配置的所有Web应用程序的正确版本。

这正是你所描述的情况。一个MySingleton实例由主Java程序加载,另一个由Jetty的类加载器加载。


这种行为是否有特定目的,我应该预计在其他服务器上也会出现吗? - Knight of Ni
对于Servlet容器,没有其他方法可以加载war文件。在其他服务器中,您无法控制main(),因此您不必担心这个问题。如果您尝试在监听器中两次执行MySingleton.getInstance(),则会获得相同的实例。如果您在servlet或上下文中的其他类中执行它,则也是如此。 - Sotirios Delimanolis
观察到确切相同的情况。如果我在ServletContextListener和REST资源中使用MySingleton,那么它是同一个实例。 - Knight of Ni
@flyer,因此由Jetty加载的类显示出您所期望的单例行为。您不能在多个类加载器之间具有该期望。 - Sotirios Delimanolis
webAppContext.setParentLoaderPriority(true); 解决了问题。我只看到了一个堆栈跟踪,太棒了!谢谢。 - Knight of Ni

2
在构造函数中打印堆栈跟踪可以让您了解实例化的位置和顺序。单例模式是一种危险的东西,通常最好选择其他方式。

2
您好!这段文本的翻译如下:

您的 ServletContextListener 和主类由不同的类加载器加载。每当加载一个类时,它的静态块都会被执行,因此您将获得两个实例...

如果要确保单例由相同的类加载器加载,则必须自己指定类加载器。

请查看此链接以获取更多详细信息http://www.javaworld.com/article/2073352/core-java/simply-singleton.html?page=2


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接