views:

208

answers:

5

Edit: I've figured out the constructor for the singleton is getting called multiple times so it appears the classes are getting loaded more than once by separate class loaders. How can I make a global singleton in Tomcat? I've been googling, but no luck so far.

I have a singleton object that I construct like thus:

private static volatile KeyMapper mapper = null;

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

    return mapper;
}

The class KeyMapper is basically a synchronized wrapper to HashMap with only two functions, one to add a mapping and one to remove a mapping. When running in Tomcat 6.24 on my 32bit Windows machine everything works fine. However when running on a 64 bit Linux machine (CentOS 5.4 with OpenJDK 1.6.0-b09) I add one mapping and print out the size of the HashMap used by KeyMapper to verify the mapping got added (i.e. verify size = 1). Then I try to retrieve the mapping with another request and I keep getting null and when I checked the size of the HashMap it was 0. I'm confident the mapping isn't accidentally being removed since I've commented out all calls to remove (and I don't use clear or any other mutators, just get and put).

The requests are going through Tomcat 6.24 (configured to use 200 threads with a minimum of 4 threads) and I passed -Xnoclassgc to the jvm to ensure the class isn't inadvertently getting garbage collected (jvm is also running in -server mode). I also added a finalize method to KeyMapper to print to stderr if it ever gets garbage collected to verify that it wasn't being garbage collected.

I'm at my wits end and I can't figure out why one minute the entry in HashMap is there and the next it isn't :(

+1  A: 

Have you tried removing the outer check

if(mapper == null)
{

Thereby always hitting the Synchronized point, it's subtle stuff but possibly you're hitting the double-checked locking idiom problem. Described here and in many other articles.

Must admit I've never seen the problem actually bite someone before, but this sure sounds like it.

djna
And if he doesn't want to hit the synchronized block every single time there's always the static block during construction. Not an answer to the question to be sure, but perhaps a solution to the problem.
extraneon
Ah, that's exactly what the author suggests as a solution, right at the bottom.
extraneon
+1  A: 

With this solution, the JVM guarantees that it's only one mapper and that's it's initialized before use.

public enum KeyMapperFactory {

    ;

    private static KeyMapper mapper = new LocalMemoryMapper();  

    public static KeyMapper getMapper() {
        return mapper;
    }
}
Espen
I tried this and I'm still getting the same problem. I put a print statement in the KeyMapper constructor and I can verify that the constructor is getting called twice. My code looks exactly like what you posted and it is the only place I create an instance of the class. My guess is the problem is something with the setup of the JVM, Tomcat or the class loader
jwegan
Can you try to add the print statement on the line after you create the mapper instead? Maybe it's called from somewhere else.If the above didn't solve your problem, I believe your code is running in several classloaders.
Espen
+1  A: 

This may not be the cause of your problem but you are using the faulty double-checked locking. See this,

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

ZZ Coder
From the same article they mention adding 'volatile' to the variable declaration fixes this issue in Java 1.5 and greater and I am using volatile.
jwegan
+4  A: 

Another wild guess: is it possible the two requests are being served by different copies of your web app? Each would be in its own ClassLoader and thus have a different copy of the singleton.

Sean Owen
I've verified that the singleton is being instantiated twice and when I moved the code into $TOMCAT_HOME/lib I was able to get the singleton to work so it is clearly a class loader issue. My app is just a bunch of JSPs and I don't define any servlet in the web.xml. Do you have any ideas on how to make requests to my app use the same class loader?
jwegan
Looks like this was it, glad you found it.Definitely don't put your app code in Tomcat's lib/ directory in the long-term.
Sean Owen
A: 

I found a rather poor fix. I exported my code as a JAR and put it in $TOMCAT/lib and that worked. This is clearly a class loader issue.

Edit: Figured out the solution

Ok, I finally figured out the problem.

I had made my application the default application for the server by adding a to server.xml and setting the path to "". However, when I was accessing it through the URL http://localhost/somepage.jsp for somethings, but also the URL http://localhost/appname/anotherpage.jsp for other things.

Once I changed all the URLs to use http://localhost/ instead of http://localhost/appname the problem was fixed.

jwegan