2014-01-16 5 views
23

Ich gerade diesen Beitrag zu lesen: What's the advantage of a Java-5 ThreadPoolExecutor over a Java-7 ForkJoinPool? und fühlte, dass die Antwort nicht gerade genug ist.Java's Fork/Join vs ExecutorService - wann welche verwenden?

Können Sie in einfacher Sprache und Beispielen erklären, was sind die Kompromisse zwischen Java 7 Fork-Join-Framework und die älteren Lösungen?

Ich las auch die # 1-Hit Google zum Thema Java Tip: When to use ForkJoinPool vs ExecutorService von javaworld.com aber der Artikel keine Antwort auf die Titelfrage wenn es meist über api Unterschiede spricht ...

Antwort

27

Fork-Join können Sie Einfaches Ausführen von Divide- und Conquer-Jobs, die manuell implementiert werden müssen, wenn Sie sie in ExecutorService ausführen möchten. In der Praxis wird ExecutorService normalerweise verwendet, um viele unabhängige Anfragen (auch bekannt als Transaktion) gleichzeitig zu verarbeiten, und fork-join, wenn Sie einen kohärenten Job beschleunigen möchten.

+5

+1 Fork-Join löst eine bestimmte Art von Problem. Wenn Sie diese Art von Problem nicht haben, verwenden Sie ExecutorService, da dies Fork-Join sowieso verwendet. –

+0

@JakubK OK. Was passiert, wenn ich ein 1000x1000 Bild zur Verarbeitung habe? Ich kann es vorne oder in D & C-Mode teilen. Was dann? Ich habe auch über Vergleiche nachgedacht - viele Aufgaben vs. wenige, langlebige vs. kurze, gleichgroße Probleme vs. nicht usw. – Parobay

+0

Wenn Sie alle Teile getrennt verarbeiten und dann Ergebnisse kombinieren können, sollten Sie fork-join verwenden –

22

Fork-Join ist besonders gut für rekursive Probleme, wo eine Aufgabe beinhaltet, Teilaufgaben ausführen und dann ihre Ergebnisse verarbeiten. (Dies wird typischerweise "teile und herrsche" genannt ... aber das enthüllt nicht die wesentlichen Eigenschaften.)

Wenn Sie versuchen, ein rekursives Problem wie dieses mit konventionellem Threading (zB über einen ExecutorService) zu lösen, landen Sie mit Threads, die darauf warten, dass andere Threads ihnen Ergebnisse liefern.

Auf der anderen Seite, wenn das Problem nicht diese Eigenschaften hat, gibt es keinen wirklichen Vorteil von der Verwendung von Gabel-Join.


Here's a "Java Tips" article that goes into more detail:

4

Rahmen Fork-Join ist eine Erweiterung Rahmen Testamentsvollstreckers warten "Themen in rekursiven Multi-Threaded-Programme besonders ansprechen. Tatsächlich reichen die neuen Fork-Join-Framework-Klassen alle von den vorhandenen Klassen des Executor-Frameworks aus.

Es gibt zwei Eigenschaften von zentraler Bedeutung für Fork-Join Rahmen

  • Arbeit Diebstahl (Ein Leerlauf-Thread stiehlt Arbeit von einem Thread Aufgaben mit Warteschlange mehr als es zur Zeit verarbeiten kann)
  • Fähigkeit zur rekursiv zersetzen die Aufgaben und sammle die Ergebnisse. (Offensichtlich muss diese Anforderung zusammen mit der Konzeption des Begriffs der Parallelverarbeitung taucht hat ... aber es fehlte ein soliden Umsetzungsrahmen in Java bis Java 7)

Wenn die parallelen Verarbeitungsanforderungen streng sind rekursiv, es gibt keine andere Wahl, als Fork-Join zu wählen, ansonsten sollte entweder der Executor oder das Fork-Join-Framework funktionieren, obwohl Fork-Join die Ressourcen besser ausnutzen kann, weil die untätigen Threads einige Aufgaben von belebteren Aufgaben "stehlen" Fäden.

6

Java 8 stellt eine weitere API in Executors

static ExecutorService newWorkStealingPool() 

Erzeugt ein arbeits Stehlen Thread-Pool alle verfügbaren Prozessoren als Ziel Parallelität der Ebene durch.

Mit dieser API bietet Executors verschiedene Arten von ExecutorService Optionen.

Je nach Ihren Anforderungen können Sie eine davon auswählen. Oder Sie können nach ThreadPoolExecutor Ausschau halten, das eine bessere Kontrolle über Mechanismen der begrenzten Aufgabenschlangen-Warteschlange, RejectedExecutionHandler, bietet.

  1. static ExecutorService newFixedThreadPool(int nThreads)

    erstellt einen Thread-Pool, der eine feste Anzahl von Threads wieder verwendet aus einer gemeinsamen unbeschränkten Warteschlange arbeitet.

  2. static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

    Erstellt einen Thread-Pool, der Befehle terminieren können nach einer bestimmten Verzögerung laufen, oder periodisch auszuführen.

  3. static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)

    erstellt einen Thread-Pool, die neuen Themen nach Bedarf erzeugt, sondern wird zuvor konstruierten Fäden wiederzuzuverwenden, wenn sie verfügbar sind, und verwendet den vorgesehenen Thread neue Threads zu erzeugen, wenn erforderlich.

  4. static ExecutorService newWorkStealingPool(int parallelism)

    Erstellt einen Thread-Pool, der genug Threads zu unterstützen den gegebenen Parallelität Pegel hält, und mehrere Warteschlangen Anstoß zu reduzieren verwenden.

sind Jede dieser APIs gezielt jeweiligen geschäftlichen Anforderungen Ihrer Anwendung zu erfüllen. Welche Sie verwenden, hängt von Ihrer Anwendungsfallanforderung ab.

z.B.

  1. Wenn Sie alle eingereichten Aufgaben in der Reihenfolge ihres Eintreffens zu verarbeiten, verwenden Sie einfach newFixedThreadPool(1)

  2. Wenn Sie die Leistung der großen Berechnung der rekursiven Aufgaben optimieren möchten, verwenden Sie ForkJoinPool oder newWorkStealingPool

  3. Wenn Sie einige Aufgaben in Zukunft periodisch oder zu einem bestimmten Zeitpunkt ausführen möchten, verwenden Sie newScheduledThreadPool

Werfen Sie einen Blick auf eine weitere schöne article von PeterLawrey auf ExecutorService Anwendungsfälle.

Verwandte SE Frage:

java Fork/Join pool, ExecutorService and CountDownLatch

4

Fork Join ist eine Implementierung von ExecuterService. Der Hauptunterschied besteht darin, dass diese Implementierung einen DEQUE-Worker-Pool erstellt. Wo Aufgabe von der einen Seite eingefügt wird, aber von jeder Seite zurückgezogen wird. Das heißt, wenn Sie new ForkJoinPool() erstellt haben, sucht es nach der verfügbaren CPU und erstellt so viele Worker-Threads. Es verteilt dann die Last gleichmäßig über jeden Thread. Aber wenn ein Thread langsam arbeitet und andere schnell sind, werden sie die Aufgabe aus dem langsamen Thread auswählen. von der Rückseite. Die folgenden Schritte veranschaulichen das Stehlen besser.

Stufe 1 (anfänglich):
W1 -> 5,4,3,2,1
W2 -> 10,9,8,7,6

Stufe 2:
W1 -> 5,4
W2 -> 10,9,8,7,

Stufe 3:
W1 -> 10,5,4
W2 -> 9,8,7,

Während Executor servi ce erstellt die angeforderte Thread-Nummer und wendet eine blockierende Warteschlange an, um alle verbleibenden wartenden Aufgaben zu speichern. Wenn Sie cachedExecuterService verwendet haben, wird für jeden Job ein einzelner Thread erstellt, und es gibt keine Warteschlange.

Verwandte Themen