2010-05-03 4 views
2

Édition: J'ai découvert que le constructeur du singleton est appelé plusieurs fois, de sorte qu'il semble que les classes soient chargées plus d'une fois par des chargeurs de classe distincts. Comment puis-je faire un singleton global dans Tomcat? J'ai été googling, mais pas de chance jusqu'à présent.Accès à une variable globale dans un serveur Tomcat multithread

J'ai un objet singleton que je construis comme ainsi:

private static volatile KeyMapper mapper = null; 

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

    return mapper; 
} 

La Miniclip de classe est essentiellement un wrapper synchronisé à hashmap avec seulement deux fonctions, l'un pour ajouter un mappage et l'autre pour supprimer une correspondance. Lors de l'exécution de Tomcat 6.24 sur mon ordinateur Windows 32 bits tout fonctionne bien. Cependant, lors de l'exécution sur une machine Linux 64 bits (CentOS 5.4 avec OpenJDK 1.6.0-b09), j'ajoute un mappage et j'imprime la taille de la HashMap utilisée par KeyMapper pour vérifier que le mappage a été ajouté (c'est-à-dire verify size = 1). Ensuite, j'essaie de récupérer le mapping avec une autre requête et je continue à obtenir null et quand j'ai vérifié la taille de la HashMap c'était 0. Je suis confiant que le mapping n'est pas accidentellement enlevé puisque j'ai commenté tous les appels à enlever (et je n'utilise pas clair ou d'autres mutateurs, juste obtenir et mettre). Les demandes passent par Tomcat 6.24 (configuré pour utiliser 200 threads avec un minimum de 4 threads) et j'ai passé -Xnoclassgc au jvm pour m'assurer que la classe ne récupère pas les ordures par inadvertance (jvm est également en cours d'exécution - mode serveur). J'ai également ajouté une méthode de finalisation à KeyMapper pour imprimer sur stderr si jamais elle était collectée pour vérifier qu'elle n'était pas collectée.

Je suis à mon esprit fin et je ne peux pas comprendre pourquoi une minute, l'entrée dans HashMap est là et la suivante il n'est pas :(

+1

Je pense que votre bug est ailleurs. –

Répondre

0

J'ai trouvé une solution plutôt mauvaise. J'ai exporté mon code en tant que JAR et l'ai mis dans $ TOMCAT/lib et cela a fonctionné. C'est clairement un problème de chargeur de classe.

Edit: Figured la solution

Ok, j'ai finalement compris le problème.

J'avais fait de mon application l'application par défaut pour le serveur en ajoutant un à server.xml et en définissant le chemin à "".Cependant, quand j'y accédais par l'URL http://localhost/somepage.jsp pour quelque chose, mais aussi l'URL http://localhost/appname/anotherpage.jsp pour d'autres choses.

Une fois que j'ai changé toutes les URL pour utiliser http://localhost/ au lieu de http://localhost/appname le problème a été corrigé.

1

Avez-vous essayé d'enlever le contrôle externe

if(mapper == null) 
{ 

de ce fait frapper toujours le point synchronisé, il est des choses subtiles mais peut-être vous frapper le problème de langage de verrouillage revérifié. Décrite here et dans de nombreux autres articles.

Je dois admettre que je ne l'ai jamais vu le problème en fait mord quelqu'un avant, mais ça sonne comme ça.

+1

Et s'il ne veut pas frapper le bloc synchronisé à chaque fois, il y a toujours le bloc statique pendant la construction. Pas une réponse à la question pour être sûr, mais peut-être une solution au problème. – extraneon

+0

Ah, c'est exactement ce que l'auteur suggère comme une solution, tout en bas. – extraneon

1

Avec cette solution, la JVM garantit qu'il s'agit d'un seul mappeur et qu'il est initialisé avant utilisation.

public enum KeyMapperFactory { 

    ; 

    private static KeyMapper mapper = new LocalMemoryMapper(); 

    public static KeyMapper getMapper() { 
     return mapper; 
    } 
} 
+0

J'ai essayé ceci et je reçois toujours le même problème. Je mets une instruction print dans le constructeur KeyMapper et je peux vérifier que le constructeur est appelé deux fois. Mon code ressemble exactement à ce que vous avez posté et c'est le seul endroit où je crée une instance de la classe. Je suppose que le problème est quelque chose avec la configuration de la JVM, Tomcat ou le chargeur de classe – jwegan

+0

Pouvez-vous essayer d'ajouter l'instruction d'impression sur la ligne après avoir créé le mappeur à la place? Peut-être que ça s'appelle d'ailleurs. Si ce qui précède n'a pas résolu votre problème, je crois que votre code fonctionne dans plusieurs chargeurs de classe. – Espen

1

Ceci n'est peut-être pas à l'origine de votre problème, mais vous utilisez le système de verrouillage à double contrôle défectueux. Voir cela,

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

+0

Dans le même article, ils mentionnent l'ajout de 'volatile' à la déclaration de variable corrige ce problème dans Java 1.5 et supérieur et j'utilise volatile. – jwegan

5

Une autre conjecture sauvage: est-il possible les deux demandes sont servis par différentes copies de votre application web? Chacun serait dans son propre ClassLoader et aurait donc une copie différente du singleton.

+0

J'ai vérifié que le singleton est instancié deux fois et quand j'ai déplacé le code dans $ TOMCAT_HOME/lib j'ai réussi à faire fonctionner le singleton donc c'est clairement un problème de chargeur de classe. Mon application est juste un tas de JSP et je ne définis aucune servlet dans le fichier web.xml. Avez-vous des idées sur la façon de faire des demandes à mon application utilisent le même chargeur de classe? – jwegan

+0

On dirait que c'était ça, content que vous l'ayez trouvé. Ne placez définitivement pas votre code d'application dans le répertoire lib/de Tomcat à long terme. –