2009-07-19 16 views
3

J'ai une méthode qui prend un tableau de requêtes, et j'ai besoin de les exécuter sur différentes API Web de moteur de recherche, comme Google ou Yahoo. Afin de paralléliser le processus, un thread est généré pour chaque requête, qui est ensuite join ed à la fin, puisque mon application ne peut que continuer après J'ai les résultats de toutes les requêtes. J'ai actuellement quelque chose le long de ces lignes:Opération de recherche multithread

public abstract class class Query extends Thread { 
    private String query; 

    public abstract Result[] querySearchEngine(); 
    @Override 
    public void run() { 
     Result[] results = querySearchEngine(query); 
     Querier.addResults(results); 
    } 

} 

public class GoogleQuery extends Query { 
    public Result querySearchEngine(String query) { 
     // access google rest API 
    } 
} 

public class Querier { 
    /* Every class that implements Query fills this array */ 
    private static ArrayList<Result> aggregatedResults; 

    public static void addResults(Result[]) { // add to aggregatedResults } 

    public static Result[] queryAll(Query[] queries) { 
     /* for each thread, start it, to aggregate results */ 
     for (Query query : queries) { 
      query.start(); 
     } 
     for (Query query : queries) { 
      query.join(); 
     } 
     return aggregatedResults; 
    } 
} 

Récemment, j'ai trouvé qu'il ya une nouvelle API Java en pour faire des travaux simultanés. A savoir, l'interface Callable, FutureTask et ExecutorService. Je me demandais si cette nouvelle API est celle qui devrait être utilisée, et si elles sont plus efficaces que les traditionnelles, Runnable et Thread.

Après avoir étudié cette nouvelle API, je suis venu avec le code suivant (version simplifiée):

public abstract class Query implements Callable<Result[]> { 
     private final String query; // gets set in the constructor 

     public abstract Result[] querySearchEngine(); 
     @Override 
     public Result[] call() { 
      return querySearchEngine(query); 
     } 
    } 

public class Querier { 
     private ArrayList<Result> aggregatedResults; 

     public Result[] queryAll(Query[] queries) { 
      List<Future<Result[]>> futures = new ArrayList<Future<Result[]>>(queries.length); 
      final ExecutorService service = Executors.newFixedThreadPool(queries.length); 
      for (Query query : queries) { 
       futures.add(service.submit(query)); 
      } 
      for (Future<Result[]> future : futures) { 
       aggregatedResults.add(future.get()); // get() is somewhat similar to join? 
      } 
      return aggregatedResults; 
     } 
    } 

Je suis nouveau à cette API d'accès concurrentiel, et je voudrais savoir s'il y a quelque chose qui peut être amélioré dans le code ci-dessus, et si c'est mieux que la première option (en utilisant Thread). Il y a des classes que je n'ai pas explorées, telles que FutureTask, et cetera. J'aimerais avoir des conseils à ce sujet également.

+0

Ça me va, pas sûr que je changerais quoi que ce soit dans ton second exemple. Dans votre premier exemple, j'allongerais Runnable et pas Thread, mais ce n'est que du nitpicking. –

+0

+1, c'est assez bon pour moi. – akarnokd

Répondre

7

Plusieurs problèmes avec votre code.

  1. Vous devriez probablement utiliser la méthode ExecutorService.invokeAll(). Le coût de création de nouveaux threads et d'un nouveau pool de threads peut être important (même s'il n'est peut-être pas comparé à l'appel de moteurs de recherche externes). invokeAll() peut gérer les threads pour vous.
  2. Vous ne voulez probablement pas mélanger les baies et les génériques.
  3. Vous appelez aggregatedResults.add() au lieu de addAll().
  4. Vous n'avez pas besoin d'utiliser des variables membres lorsqu'elles peuvent être locales à l'appel de la fonction queryAll().

Donc, quelque chose comme ce qui suit devrait fonctionner:

public abstract class Query implements Callable<List<Result>> { 
    private final String query; // gets set in the constructor 

    public abstract List<Result> querySearchEngine(); 
    @Override 
    public List<Result> call() { 
     return querySearchEngine(query); 
    } 
} 

public class Querier { 
    private static final ExecutorService executor = Executors.newCachedThreadPool(); 

    public List<Result> queryAll(List<Query> queries) { 
     List<Future<List<Result>>> futures = executor.submitAll(queries); 
     List<Result> aggregatedResults = new ArrayList<Result>(); 
     for (Future<List<Result>> future : futures) { 
      aggregatedResults.addAll(future.get()); // get() is somewhat similar to join? 
     } 
     return aggregatedResults; 
    } 
} 
+0

La modification du pool de threads mis en cache peut ne pas être la meilleure option, car votre application est liée à l'E/S, car la plupart des moteurs de recherche sont très rapides et répondent rapidement. . – akarnokd

+0

@ kd304: En effet, les moteurs de recherche que j'utilise sont assez rapides (Google et Yahoo, actuellement). Cependant, j'utilise beaucoup de requêtes, d'où le besoin de concurrence. Quel est votre conseil à ce sujet? D'après ce que j'ai lu sur le javadoc de la méthode newCachedThreadPool, il semble correspondre à mes objectifs. Mais encore une fois, je suis assez nouveau à cette API. –

+0

@Avi: Merci beaucoup pour les suggestions! –

4

Comme une amélioration futher, vous pouvez regarder dans un CompletionService Il découple l'ordre de la soumission et la récupération, au lieu de placer tous les résultats futurs sur une file d'attente à partir de laquelle vous prenez des résultats dans l'ordre où ils sont terminés ..

+0

Étant donné que l'application ne peut continuer dans ce cas que lorsque * chaque * tâche est terminée, un service Completion n'est peut-être pas approprié ici. – Avi

+0

@Avi: Je ne suis pas d'accord, ce n'est pas aussi beau que le futur.obtenir(). – akarnokd

+0

@ kd304: Quelle méthode de CompletionService utiliseriez-vous pour obtenir tous les résultats d'un ensemble de tâches? – Avi

3

Puis-je vous suggère d'utiliser Future.get() with a timeout?

Sinon, il ne vous prendra un moteur de recherche étant insensible à apporter tout un coup d'arrêt (il n'a même pas besoin d'être un problème de moteur de recherche si, par exemple, vous avez un problème de réseau à votre fin)

+0

Merci. Quelle est la valeur de délai d'attente type utilisée pour ce type d'opérations? –

+0

Je pense que vous devez vous demander combien de temps vous seriez prêt à attendre :-) Faites-le configurable et réglez-le à (disons) 10 fois le temps de réponse normal. –

+0

Je pense que la bonne couche dans le code pour le délai d'attente n'est pas Future.get(), c'est l'appel réseau (HTTP?) Au moteur de recherche lui-même. Si le moteur de recherche arrive à expiration, mieux il devrait être attrapé là, et ne pas attacher un fil qui n'est plus nécessaire. – Avi