2009-07-19 14 views
3

Ich habe eine Methode, die eine Reihe von Abfragen benötigt, und ich muss sie gegen verschiedene Suchmaschinen-Web-APIs ausführen, wie Google oder Yahoo. Um den Prozess parallel zu machen, wird für jede Abfrage ein Thread erzeugt, der dann am Ende join ed wird, da meine Anwendung nur weiter nach ich habe die Ergebnisse alle abfragen kann. Im Moment habe ich etwas in dieser Richtung:Multithread-Suche

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; 
    } 
} 

Kürzlich habe ich festgestellt, dass es eine neue API in Java für die gleichzeitige Arbeit tun. Nämlich die Callable Schnittstelle, FutureTask und ExecutorService. Ich habe mich gefragt, ob diese neue API die ist, die verwendet werden sollte, und wenn sie effizienter als die traditionellen sind, Runnable und Thread.

Nach dieser neuen API zu studieren, kam ich mit dem folgenden Code (vereinfachte Version) bis:

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; 
     } 
    } 

Ich bin neu in dieser Gleichzeitigkeit API, und ich würde gerne wissen, ob es etwas gibt, das kann be verbesserte in dem obigen Code, und wenn es besser ist als die erste Option (mit Thread). Es gibt einige Klassen, die ich nicht untersucht habe, wie FutureTask und so weiter. Ich würde auch gerne einen Rat dazu hören.

+0

Sieht gut aus, ich bin mir nicht sicher, ob ich in deinem zweiten Beispiel etwas ändern würde. In Ihrem ersten Beispiel würde ich Runnable und nicht Thread erweitern, aber das ist nur pingelig. –

+0

+1, Es ist gut genug für mich. – akarnokd

Antwort

7

Mehrere Probleme mit Ihrem Code.

  1. Sie sollten wahrscheinlich die ExecutorService.invokeAll() -Methode verwenden. Die Kosten für das Erstellen neuer Threads und eines neuen Thread-Pools können beträchtlich sein (obwohl dies möglicherweise nicht mit dem Aufruf externer Suchmaschinen verglichen wird). invokeAll() kann die Threads für Sie verwalten.
  2. Vermutlich möchten Sie Arrays und Generika nicht mischen.
  3. Sie rufen aggregatedResults.add() anstelle von addAll() auf.
  4. Sie müssen keine Mitgliedsvariablen verwenden, wenn sie für den Funktionsaufruf queryAll() lokal sein könnten.

Also, so etwas wie die folgenden funktionieren sollte:

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

Ändern in den Cache-Thread-Pool möglicherweise nicht die beste Option, da Ihre Anwendung IO-gebunden ist, wie die meisten Suchmaschinen sind sehr schnell und werden sofort reagieren . – akarnokd

+0

@ kd304: Tatsächlich sind die Suchmaschinen, die ich verwende, ziemlich schnell (Google und Yahoo, zurzeit). Ich benutze jedoch viele Abfragen, daher die Notwendigkeit für Nebenläufigkeit. Was raten Sie dazu? Nach dem, was ich auf dem Javadoc der newCachedThreadPool-Methode gelesen habe, scheint es meinen Zwecken zu entsprechen. Aber andererseits bin ich zu dieser API ziemlich neu. –

+0

@Avi: Vielen Dank für die Vorschläge! –

4

Als weitere Verbesserung, Sie CompletionService Es abkoppelt in mit der Reihenfolge der Einreichung und Abrufen alle, anstatt Platzierung der zukünftigen Ergebnisse auf einer Warteschlange aussehen könnte, aus dem Sie die Ergebnisse in der Reihenfolge nehmen sie abgeschlossen sind ..

+0

Da die Anwendung in diesem Fall nur fortgesetzt werden kann, nachdem * jede * Aufgabe abgeschlossen wurde, ist ein CompletionService möglicherweise hier nicht geeignet. – Avi

+0

@Avi: Ich stimme nicht zu, es ist einfach nicht so schön wie die Zukunft.bekommen(). – akarnokd

+0

@ kd304: Welche Methode von CompletionService würden Sie verwenden, um alle Ergebnisse einer Reihe von Aufgaben zu erhalten? – Avi

3

Kann ich schlage vor, Sie Future.get() with a timeout verwenden?

Sonst wird es dauern nur eine Suchmaschine nicht mehr reagiert wird alles zum Stillstand zu bringen (es braucht nicht einmal eine Suchmaschine Problem zu sein, wenn, sagen wir, Sie ein Netzwerkproblem an Ihrem Ende haben)

+0

Danke. Was ist der typische Zeitüberschreitungswert, der für diese Art von Operationen verwendet wird? –

+0

Ich denke, du musst dich fragen, wie lange du bereit wärst zu warten :-) Mach es konfigurierbar und setze es (sagen wir mal) auf die normale Reaktionszeit. –

+0

Ich denke, dass die richtige Schicht im Code für das Timeout nicht Future.get() ist, ist es das Netzwerk (HTTP?) Aufruf an die Suchmaschine selbst. Wenn die Suchmaschine eine Zeitüberschreitung hat, sollte sie besser dort abgefangen werden und keinen Thread mehr binden, der nicht mehr benötigt wird. – Avi