30 votes

Qu'est-ce qui peut provoquer une IllegalMonitorStateException à l'intérieur d'un bloc synchronisé ?

Nous avons rencontré une exception extrêmement surprenante aujourd'hui. A l'intérieur d'un bloc synchronisé, nous appelons wait() et il jette IllegalMonitorStateException . Qu'est-ce qui peut causer cela ?

Cela se passe dans un code source ouvert bien testé : http://svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/Mux.java?view=markup#l222

Nous avons éliminé les causes évidentes :

  • sommes-nous synchronisés sur la bonne variable ? Oui, c'est muxLock
  • s'agit-il d'une variable mutable ? Non, muxLock est définitif
  • Utilisons-nous des drapeaux JVM bizarres "-XX :" ? JVM qui pourrait affecter le comportement du moniteur ? Non, mais nous lançons la JVM intégrée dans une application C++ via JNI.
  • s'agit-il d'une JVM étrange ? Non, c'est la JRE 1.6.0_25 win/x64 de Sun.
  • Est-ce un bug connu de la JVM ? Je ne trouve rien de pertinent sur http://bugs.sun.com/bugdatabase

Donc, j'essaie de penser à des explications plus farfelues.

  • Est-ce qu'une erreur hors-mémoire non attrapée pourrait causer un désordre dans l'état du moniteur ? Nous examinons cette question, mais nous ne voyons pas encore de preuves d'erreurs de mémoire.

UPDATE : (sur la base d'un commentaire)

J'ai également vérifié à partir de la trace de la pile et du point d'arrêt que le thread se trouve bien à l'intérieur du bloc synchronisé lorsque l'exception est levée. Ce n'est pas le cas qu'un autre code non lié émette l'exception (à moins que quelque chose ne perturbe VRAIMENT Eclipse !)

6voto

Jan de Vos Points 1615

La seule chose suspecte que je vois est que vous passez une référence à 'this' à un autre objet dans votre constructeur. Est-il possible (en fait, pas improbable) que, par une réorganisation bizarre des choses, si un autre thread obtient cette référence à 'this' et appelle la méthode qui utilise le muxlock, les choses peuvent aller extrêmement mal.

El Spécification du langage Java est assez spécifique à ce sujet :

On considère qu'un objet est complètement initialisé lorsque son constructeur se termine . Un thread qui ne peut voir une référence à un objet qu'après que cet objet ait été complètement initialisé est assuré de voir les valeurs correctement initialisées pour les champs finaux de cet objet.

En d'autres termes, si un autre thread s'empare de la référence 'this' avant que le constructeur ne soit terminé, le champ final 'muxlock' pourrait ne pas être correctement initialisé. En général, la publication d'une référence à 'this' avant la fin du constructeur peut être assez dangereuse, surtout dans les situations de threads.

Une discussion potentiellement utile sur ces questions : http://madpropellerhead.com/random/20100328-java-final-fields-are-not-as-final-as-you-may-think

Pour une discussion générale plus ancienne, mais toujours utile, sur les raisons pour lesquelles publier 'this' dans un constructeur est une très mauvaise idée en général, voir par exemple : http://www.ibm.com/developerworks/java/library/j-jtp0618/index.html

2voto

firegnom Points 710

http://svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/Mux.java?r1=1069292&r2=1135026&diff_format=h

Ici, je peux voir que le délai d'attente a été ajouté récemment.

assurez-vous que startTimeout est > à 0, sinon vous attendrez (0) ou attendre (-n), ce qui provoquera probablement une IllegalMonitorStateException.

EDIT : Ok, ce qui précède est un désastre mais essayons ceci :

nous sommes dans le constructeur de Mux : http://svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/Mux.java?view=markup

ligne 176 nous créons SocketChannelConnectionIO et passons ceci après cela nous nous interrompons et un autre thread prend le relais.

dans le constructeur de SocketChannelConnectionIO défini ici : http://svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/SocketChannelConnectionIO.java?view=markup ligne 112 nous enregistrons le canal avec le nouveau handler().

Le handler reçoit quelque chose sur le canal et la fonction, disons la fonction handleReadReady est exécutée, nous synchronisons sur muxLock.

maintenant nous sommes toujours dans le constructeur donc l'objet dans la finale est toujours mutable ! !! supposons qu'il change, maintenant nous avons quelque chose qui attend un autre muxLock

Un scénario sur un million

EDITAR

http://svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/Mux.java?revision=1135026&view=co

Mux(SocketChannel channel,
    int role, int initialInboundRation, int maxFragmentSize)
    throws IOException
    {
    this.role = role;
    if ((initialInboundRation & ~0x00FFFF00) != 0) {
        throw new IllegalArgumentException(
        "illegal initial inbound ration: " +
        toHexString(initialInboundRation));
    }
    this.initialInboundRation = initialInboundRation;
    this.maxFragmentSize = maxFragmentSize;

    //LINE BELOW IS CAUSING PROBLEM it passes this to SocketChannelConnectionIO
    this.connectionIO = new SocketChannelConnectionIO(this, channel);

    //Lets assume it stops here we are still in constructor
    //and we are not in synchronized block

    directBuffersUseful = true;
    }

maintenant dans le constructeur de SocketChannelConnectionIO http://svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/SocketChannelConnectionIO.java?revision=1069292&view=co

SocketChannelConnectionIO(Mux mux, SocketChannel channel)
    throws IOException
{
    super(mux);
    channel.configureBlocking(false);
    this.channel = channel;
    //Line below we are registering to the channel with mux that is still mutable
    //this is the line that actually is causing the problem move that to 
    // start() and it should work 
    key = selectionManager.register(channel, new Handler());
}

déplacer ce code vers start() devrait fonctionner key = selectionManager.register(channel, new Handler()); (je suppose que start est exécuté quand nous voulons commencer la prospection).

/**
 * Starts processing connection data.
 */
void start() throws IOException {
    key = selectionManager.register(channel, new Handler());
    key.renewInterestMask(SelectionKey.OP_READ);
}

Mais il serait bien mieux de ne pas créer SocketChannelConnectionIO dans le constructeur de mux mais peut-être quelque part après cela, de même pour le second constructeur créant StreamConnectionIO avec ceci

1voto

Gábor Lipták Points 3745

A mon avis, la réponse est soit un bug, soit quelqu'un a changé l'objet derrière la référence bien qu'elle soit finale. Si vous pouvez le reproduire, je recommande de mettre un point d'arrêt en lecture/écriture sur le champ muxlock pour voir s'il est touché ou non. Vous pourriez vérifier l'identityhashcode du muxlock dans la première ligne du bloc synchronisé, et avant d'attendre et de notifier avec des entrées de journal appropriées ou des points d'arrêt. Avec la réflexion, vous pouvez changer les références finales. Citation de http://download.oracle.com/javase/6/docs/api/java/lang/reflect/Field.html :

" Si le champ sous-jacent est final, la méthode lève une IllegalAccessException. sauf si setAccessible(true) a réussi pour ce champ et ce champ est non statique. La définition d'un champ final de cette manière n'a de sens que pendant la désérialisation ou la reconstruction d'instances de classes dont les champs finaux sont vides, avant qu'elles ne soient rendues accessibles par d'autres parties d'un programme. L'utilisation dans tout autre contexte peut avoir des effets imprévisibles, y compris des cas dans lesquels d'autres parties d'un programme continuent à utiliser la valeur originale de ce champ."

Peut-être que c'est un bug d'eclispe, et que pendant le débogage, le champ est modifié. Est-il reproductible en dehors d'eclispe également ? Mettez un printstractrace dans catch et voyez ce qui se passe.

1voto

Christian Points 1547

Les variables des membres ne sont pas aussi définitives qu'on pourrait l'espérer. Vous devriez d'abord placer l'objet synchronisé dans une variable locale finale. Cela n'explique pas pourquoi la variable membre est modifiée, mais si cela résout le problème, vous savez au moins que la variable membre est réellement modifiée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X