2

编辑:我发现单例的构造函数被多次调用,所以看起来这些类被单独的类加载器多次加载。如何在 Tomcat 中创建全局单例?我一直在谷歌搜索,但到目前为止没有运气。

我有一个像这样构造的单例对象:

private static volatile KeyMapper mapper = null;

public static KeyMapper getMapper()
{
    if(mapper == null)
    {
        synchronized(Utils.class)
        {
            if(mapper == null)
            {
                mapper = new LocalMemoryMapper();
            }
        }
    }

    return mapper;
}

KeyMapper 类基本上是 HashMap 的同步包装器,只有两个功能,一个用于添加映射,一个用于删除映射。在我的 32 位 Windows 机器上运行 Tomcat 6.24 时,一切正常。但是,当在 64 位 Linux 机器(CentOS 5.4 和 OpenJDK 1.6.0-b09)上运行时,我添加了一个映射并打印出 KeyMapper 使用的 HashMap 的大小,以验证是否添加了映射(即验证大小 = 1)。然后我尝试用另一个请求检索映射,但我一直为空,当我检查 HashMap 的大小时,它为 0。我确信映射不会被意外删除,因为我已经注释掉了所有删除调用(而且我不使用 clear 或任何其他变异器,只是 get 和 put)。

这些请求通过 Tomcat 6.24(配置为使用至少 4 个线程的 200 个线程),我将 -Xnoclassgc 传递给 jvm 以确保该类不会无意中收集垃圾(jvm 也在 -server 模式下运行)。我还向 KeyMapper 添加了一个 finalize 方法,以便在它被垃圾收集时打印到 stderr,以验证它没有被垃圾收集。

我束手无策,我无法弄清楚为什么一分钟 HashMap 中的条目在那里,而下一个它不是:(

4

5 回答 5

5

另一个疯狂的猜测:这两个请求是否可能由您的 Web 应用程序的不同副本提供服务?每个都是自己的ClassLoader,因此有一个不同的单例副本。

于 2010-05-03T12:36:21.017 回答
1

你试过去掉外部检查吗

if(mapper == null)
{

因此总是达到同步点,这是微妙的东西,但可能你遇到了双重检查锁定习语问题。在此处和许多其他文章中进行了描述。

必须承认我以前从未见过这个问题真的咬人,但这听起来确实像。

于 2010-05-03T09:14:29.277 回答
1

这可能不是问题的原因,但您使用的是错误的双重检查锁定。看到这个,

http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java

于 2010-05-03T12:10:33.513 回答
1

使用这个解决方案,JVM 保证它只是一个映射器,并且它在使用前被初始化。

public enum KeyMapperFactory {

    ;

    private static KeyMapper mapper = new LocalMemoryMapper();  

    public static KeyMapper getMapper() {
        return mapper;
    }
}
于 2010-05-03T11:32:12.990 回答
0

我发现了一个相当糟糕的修复。我将我的代码导出为 JAR 并将其放入 $TOMCAT/lib 中,这样就可以了。这显然是一个类加载器问题。

编辑:想出了解决方案

好的,我终于找到了问题所在。

通过向 server.xml 添加一个并将路径设置为“”,我使我的应用程序成为服务器的默认应用程序。但是,当我通过 URL http://localhost/somepage.jsp 访问它时,还通过 URL http://localhost/appname/anotherpage.jsp访问其他东西。

一旦我将所有 URL 更改为使用http://localhost/而不是http://localhost/appname,问题就解决了。

于 2010-05-03T19:48:02.593 回答