2009-07-27 6 views
0

Lorsque vous souhaitez qu'une certaine tâche soit exécutée par un autre thread, vous pouvez étendre Thread ou implémenter Runnable.Java: classe entièrement exécutée dans le second thread/IllegalMonitorStateException

J'ai essayé de créer une classe qui exécute une classe entièrement dans le deuxième thread. Cela signifie que vous pouvez appeler anyMethod() qui retourne immédiatement et qui est exécuté par le second thread.

Voici ma tentative:

import java.lang.reflect.Method; 
import java.util.ArrayList; 
import java.util.List; 
import java.util.Queue; 
import java.util.concurrent.ConcurrentLinkedQueue; 

/** 
* Extend this class to run method calls asynchronously in the second thread implemented by this class. 
* Create method(type1 param1, type2 param2, ...) and let it call this.enqueueVoidCall("method", param1, param2, ...) 
* 
* The thread executing the run-method will automatically call methodAsync with the specified parameters. 
* To obtain the return-value, pass an implementation of AsyncCallback to this.enqueueCall(). 
* AsyncCallback.returnValue() will automatically be called upon completion of the methodAsync. 
* 
*/ 
public class ThreadedClass extends Thread { 
    private static Object test; 

    private Queue<String> queue_methods = new ConcurrentLinkedQueue<String>(); 
    private Queue<Object[]> queue_params = new ConcurrentLinkedQueue<Object[]>(); 
    private Queue<AsyncCallback<? extends Object>> queue_callback = new ConcurrentLinkedQueue<AsyncCallback<? extends Object>>(); 

    private volatile boolean shutdown = false; 

/** 
* The run method required by Runnable. It manages the asynchronous calls placed to this class. 
*/ 
@Override 
public final void run() { 
    test = new Object(); 
    while (!shutdown) { 
     if (!this.queue_methods.isEmpty()) { 
      String crtMethod = queue_methods.poll(); 
      Object[] crtParamArr = queue_params.poll(); 
      String methodName = crtMethod + "Async"; 

      Method method; 
      try { 
       method = this.getClass().getMethod(methodName); 
       try { 
        Object retVal = method.invoke(this, crtParamArr); 
        AsyncCallback<? extends Object> crtCallback = queue_callback.poll(); 
        crtCallback.returnValue(retVal); 
       } catch (Exception ex) {} 
       } catch (SecurityException ex) { 
       } catch (NoSuchMethodException ex) {} 
     } else { 
      try { 
       synchronized(test) { 
        test.wait(); 
       } 
      } catch (InterruptedException ex) { 
       System.out.println("READY"); 
      } catch (Exception ex) { 
       System.out.println("READY, but " + ex.getMessage()); 
      } 
     } 
    } 
} 

/** 
* Asynchronously adds a method-call to the scheduler, specified by methodName with passed parameters 
* @param methodName The name of the currently called method. methodName + "Async" is being called 
* @param parameters Parameters you may want to pass to the method 
*/ 
protected final void enqueueVoidCall(String methodName, Object... parameters) { 
    List<Object> tmpParam = new ArrayList<Object>(); 
    for (Object crt : parameters) { 
     tmpParam.add(crt); 
    } 
    queue_methods.add(methodName); 
    queue_params.add(parameters); 
    queue_callback.add(null); 
    test.notifyAll(); 
} 

/** 
* Asynchronously adds a method-call to the scheduler, specified by methodName with passed parameters 
* @param methodName The name of the currently called method. methodName + "Async" is being called 
* @param callBack An instance of AsyncCallback whose returnValue-method is called upon completion of the task. 
* @param parameters Parameters you may want to pass to the method 
*/ 
protected final void enqueueCall(String methodName, AsyncCallback<? extends Object> callBack, Object... parameters) { 
    List<Object> tmpParam = new ArrayList<Object>(); 
    for (Object crt : parameters) { 
     tmpParam.add(crt); 
    } 
    queue_methods.add(methodName); 
    queue_params.add(parameters); 
    queue_callback.add(callBack); 
    test.notifyAll(); 
} 

/** 
* Complete the currently running task, optionally return values and eventually shut down. The instance of this object becomes unusable after this call. 
*/ 
public void shutdown() { 
    shutdown=true; 
} 

} 

Maintenant, j'ai deux classes pour tester les choses:

public class MySecondTask extends ThreadedClass { 
public void test1() { 
    this.enqueueVoidCall("test1", null); 
} 

public void test1Async() { 
    System.out.println("Start"); 
    try { 
     // do big job here 
    } catch (Exception ex) { } 
    System.out.println("Done"); 
} 
} 

Et la principale méthode à partir de la substance:

public class TestingClass { 
public static void main(String[] args) { 
    MySecondTask test = new MySecondTask(); 
    test.start(); 
    System.out.println("1. Thread [1]"); 
    // CORRECTION, SHOULD BE: 
    test.test1(); 
    // INSTEAD OF: 
    // test.test1Async(); 
    for(int q=0; q<=100000; q++) { 
     System.out.println("1:"+ new Date().getTime()+":"+ q); 
     if ((q % 1000) == 0) { 
      System.out.flush(); 
     } 
    } 
    System.err.println("1. Thread [2]"); 
} 

} 

D'une certaine façon, la sortie du deuxième thread apparaît toujours en premier (entièrement), puis le reste sort sur la console. Si les threads étaient en cours d'exécution (ce qui est le résultat escompté), la sortie console devrait être mélangée ?!

Toute idée est appréciée ainsi que des commentaires pour améliorer mon style de codage.


EDIT:

Le problème cité est tout à fait résolu.

Maintenant, je reçois une exception IllegalMonitorStateException sur la ligne où j'appelle: ThreadedClass.notifyAll().

Peut-être que j'ai eu celui avec le verrou faux. A) Pourquoi est-il nécessaire d'utiliser synchronized() autour de wait() et comment puis-je faire l'appel notifyAll() pour débloquer wait()?


grâce à l'avance et meilleures salutations

p.s .: vous tous faire un bon travail sur le débordement de la pile. tu m'as déjà aidé plusieurs fois sans le savoir, merci pour ça. continuez!

+0

Y at-il une erreur dans TestingClass.main()? Je ne vois nulle part qu'il appelle test1(). –

+0

Attendez une minute, où avez-vous obtenu ceci: objet AsyncCallback, il ne fait pas partie de Java – OscarRyz

Répondre

5

Cela signifie que vous pouvez appeler anyMethod() qui retourne immédiatement et qui est exécuté par le second fil .

Cela ressemble étrangement à travailler avec appelables, contrats à terme et les exécuteurs:

Je déteste rompre pour vous, mais vous pourriez vraiment envie de se pencher sur ce genre de choses ..

Modifier pour répondre commentaire ci-dessous

Assurez vos méthodes dans votre objet ressemblent à ceci:

private ExecutorService executorService = Executors.newCachedThreadPool(); 

public Future<SomeObject> yourMethodName(String anyArguments) { 
    return executorService.submit(
     new Callable<SomeObject>() { 
      public SomeObject call() { 
       SomeObject obj = new SomeObject(); 
       /* Your Code Here*/; 
       return obj; 
      } 
     } 
    ); 
} 
+0

merci pour le conseil. J'ai déjà entendu parler d'eux et je pense que je sais ce que font ces installations. Mon problème avec ceux-ci est que je devrais créer une autre implémentation de Runnable pour _each_ des méthodes d'une classe que je veux appeler. ou est-ce que je me trompe? Je voudrais juste pouvoir appeler myObject.method1(), peut-être plus tard myObject.method2(). Ceux-ci doivent retourner immédiatement et faire le travail d'arrière-plan de manière asynchrone. Je pensais qu'avec ma tentative d'utiliser une "file d'attente", cette tâche pourrait être accomplie ...?! – Atmocreations

+0

Jetez un oeil sur le nouveau bloc de code ci-dessus .. Ce type de mise en œuvre devrait vraiment être suffisant pour résoudre la plupart des problèmes. Si vous voulez le rendre plus effrayant/compliqué, vous pouvez ajouter un reflet pour ajouter la soumission Callables et threadpool pour vous, mais je ne le recommanderais pas. – Tim

+0

merci. à mon avis, cela me semble plus compliqué que celui que j'ai fait. mais je vais regarder de plus près pour voir si cela pourrait m'aider. – Atmocreations

0

Votre thread principal attend le retour de test.test1Async(), que faites-vous dedans?

0

Les threads Java s'exécutent différemment sur des machines différentes. Certaines machines sont préemptives, d'autres ne le sont pas. Si le second thread sort son contenu avant le premier, la machine sur laquelle votre code s'exécute est probablement non préemptive. Si vous avez besoin que les threads soient synchronisés, vous pouvez le faire, mais si vous ne vous souciez pas de la synchronisation, il n'y a aucune garantie quant à l'exécution de vos threads.

De même, test.test1Async() s'exécute dans le premier thread, pas le second. (Cela peut être ce qui tient les choses)

+0

ok je peux comprendre le premier point. mais il ne devrait pas être exécuté dans le premier thread, car le thread qui est actuellement dans la méthode d'exécution devrait l'appeler (c'est mon intention). pourquoi est-il appelé à partir du premier thread? Qu'est-ce qui ne va pas? – Atmocreations

+0

Bon, quel système d'exploitation capable de lire StackOverflow et d'exécuter une JVM aujourd'hui n'est PAS multithread préemptif? –

+0

@Atmocreations - Lorsque vous dites au second thread de démarrer, il commence son exécution dans la méthode run, mais test.test1Async() n'est pas appelé dans la méthode run, il est appelé dans la méthode principale du thread principal. Essayez de ne même pas démarrer le deuxième thread, test1Async() sera toujours exécuté. – SquareRootOf2

0

Suggestion de style: regardez java.lang.reflect.Proxy et InvocationHandler. Vous pouvez implémenter un InvocationHandler pour éviter certains éléments de réflexion auxquels vous avez affaire, et les utilisateurs peuvent appeler directement les méthodes d'interface réelles.

0

Vous appelez test1Async de manière synchrone. Si votre // bigjob est fait ici, alors il ne fonctionnera pas en parallèle avec vos autres threads. Ce que vous voulez faire est la suivante:

public class TestingClass { 
public static void main(String[] args) 
{ 
    MySecondTask test = new MySecondTask(); 
    test.start(); 
    System.out.println("1. Thread [1]"); 
    //test.test1Async(); 
    for(int q=0; q<=100000; q++) 
    { 
      System.out.println("1:"+ new Date().getTime()+":"+ q); 
      if ((q % 1000) == 0) 
      { 
        System.out.flush(); 
      } 
    } 
    System.err.println("1. Thread [2]"); 
} 

} 

Rappelez-vous, votre méthode ThreadedClass.run() va vous appeler test1.testAsynch(). Je suis vraiment surpris que vous ne voyiez pas le résultat deux fois, ou que vous ne fassiez pas de calculs.

+0

vu votre point, merci. eu un autre problème maintenant ... (vous voudrez peut-être voir mon commentaire sur les réponses d'oscar reyes) – Atmocreations

2

Vous n'êtes jamais appeler votre mécanisme d'expédition « fileté » (celui qui utilise la file d'attente, etc.)

Je suppose que vous essayez d'appeler:

test.test1(); 

Ce qui, à son tour file d'attente le l'appel à test1Async, mais par erreur vous a appelé:

test.test1Async(); 

directement faire toute l'exécution dans un seul fil.

Remplacer:

.... 
    System.out.println("1. Thread [1]"); 
    test.test1Async(); 
    for(int q=0; q<=100000; q++) 
    { 
    ... 

Avec:

.... 
    System.out.println("1. Thread [1]"); 
    test.test1(); 
    for (int q=0; q<=100000 ; q++) { 
    .... 

Sur le style de codage, pleeeeease utiliser l'accolade d'ouverture dans la même ligne que la déclaration lors du codage en Java (et JavaScript) en C#, C++ et C sont meilleurs comme vous l'avez.

Utilisez également camelCase au lieu de separete_with_underscore.

Voici un document avec plus sur Java's coding conventions.

+0

ouch, oui vous avez raison. (où est-ce que mon commentaire que je viens de publier va?) comme darthcoder a commenté la ligne que j'ai vu que quelque chose ne peut pas être juste. après une autre petite correction j'ai réussi à exécuter le code à nouveau. mais cette fois, il lance IllegalMonitorStateException sur la ligne où j'appelle notifyAll(). Bien sûr, le deuxième thread (qui appelle maintenant cette méthode) n'est pas le propriétaire du verrou, je le sais. Mais c'est à ça que sert la notification, non? pour continuer d'autres threads qui attendent cela. une idée de comment le corriger? – Atmocreations

+0

De la référence JDK: [IllegalMonitorStateException - si le thread actuel n'est pas le propriétaire de l'écran de cet objet.] –

+0

Si vous ne postez pas le stacktrace est très difficile à "deviner", mais je suppose. Dans la méthode: enqueueCall() vous appelez test.notifyAll(); Afin d'utiliser notifier tout le thread doit posséder le verrou de l'objet. – OscarRyz

1

Je vous suggère de l'implémenter exactement comme Tim l'a suggéré dans sa réponse.

Votre propre idée, bien qu'inventive, rompt beaucoup de bonnes pratiques qui rendent votre code très fragile. Il y a juste beaucoup de choses à se rappeler sinon il se cassera subtilement. Pensez au type qui viendra après vous et devrez utiliser, maintenir et étendre votre code. Pensez à ce qui se passera lorsque vous devrez revoir ce code dans un an.

Juste une courte liste de choses vous ne devriez pas faire:

  • L'extension de cette discussion est considérée comme une mauvaise pratique directement, préfèrent mettre en œuvre Runnable au lieu
  • Évitez les méthodes de codage sous forme de texte - il se brisera sur le premier refactoring
  • test1Async() devrait être privé sinon quelqu'un de nouveau à l'équipe appeler directement la dénomination
  • de méthode doit être claire - * Async() signifie généralement faire en arrière-plan pendant qu'il est en fait l'inverse
  • Fragilité générale - disons que j'ai besoin de changer test1() pour retourner int au lieu de void - vais-je vraiment me rappeler de changer l'autre méthode aussi? Est-ce que vous rappelez de le faire un an plus tard?
+0

thx pour votre réponse. D'accord, l'implémentation de Runnable au lieu d'étendre Thread est quelque chose que je pourrais facilement vivre avec ... making ... Async() private (ou protégé pour l'extensibilité) est un bon point également. pour l'instant je ne m'en souciais pas vraiment parce que je voulais juste que le code fonctionne comme prévu; o) peut-être qu'il est brisé, je suis d'accord. mais quand je l'implémente comme Tim l'a suggéré, je suppose que j'aurais encore le même problème avec le rappel des différents changements que je devrais faire en changeant le code. – Atmocreations

+0

Vous avez une bonne attitude positive qui est très cool :) En ce qui concerne la solution de Tim - l'avantage est qu'elle est très concentrée. Ainsi, par exemple, si vous voulez changer une méthode, allez dans la méthode et changez-la - et c'est tout. Pas besoin d'aller à une autre méthode, pas besoin de mettre à jour une chaîne de texte quelque part - il suffit de trouver la méthode et de la réparer. Cela peut ne pas sembler important maintenant, mais dans quelques mois vous me remercierez :) –

Questions connexes