Parallele Programmierung (3IB)
ExecutorService für asynchrone Methodenaufrufe
Callable, Future, ExecutorServiceExecutors-Factory: Fixed und Cached Threadpoolsshutdown())ScheduledExecutorService (u.a. schedule(...))Runnable und Callable bei Threadpool VerwendungCompletionService für voneinander unabhängige TasksReihenfolgen, in der die Ausdrücke berechnet werden können:
; \(3+4\) ; \(3+7\); \(1+2\) ; \(3+7\)|| \(3+4\) ; 3+7
pp.04.01-RunnableReturnExecutorService für asynchrone Methodenaufrufe: Callable und FutureWie kann ein nebenläufiger Thread ein Ergebnis abliefern?
Reihenfolgen, in der die Ausdrücke berechnet werden können:
; \(3+4\) ; \(3+7\); \(1+2\) ; \(3+7\)|| \(3+4\) ; 3+7
Callable und FutureCallable, Runnable und Future gemacht.Callable und Runnable repräsentieren die asynchron abzuarbeitende Aufgabe.Future kann das Ergebnis einer asynchronen Berechnung abgerufen werden (get())FutureTask ist eine Implementierung von Future und Runnable.ExecutorService-FrameworkExecutorService.submit Callable-Objekte an und starten call() asynchron.Future als Proxy, über den
Callable asynchron mit ExecutorService ausführenCallable und Future”pp.04.02-FutureThread-Instanziierung ist “teurer” (dauert länger) als bei anderen Klassen, denn Datenstrukturen zur Threadkontrolle müssen angelegt werden und threadlokaler Stack-Speicher angefordert werden.Thread-Objekte werden frühzeitig (z.B. beim Start) vorbereitet (“Thread Pool”). Wird ein Thread benötigt, wird einer der vorbereiteten Threads aus dem Pool genommen, mit einem Runnable-Objekt verbunden und (re-) aktiviert (statt start()).run()-Methode wird der Thread nicht vergessen und über die Garbage Collection entfernt, sondern deaktiviert und in den Thread Pool zur Wiederverwendung eingestellt.ExecutorService-FrameworkDie Klasse Executors stellt Factory-Methoden zur Erzeugung von Objekten zum ExecutorService-Interface bereit:
newCachedThreadPool()newFixedThreadPool(nThreads: int)newSingleThreadExecutor()shutdown()
ExecutorService beginnt, herunterzufahren. Der Aufruf von shutdown() ist aber asynchron (es geht direkt im Anschluss weiter im Programmablauf.isShutdown()
ExecutorService bereits fertig terminiert ist (nach shutdown()).shutdownNow()
ExecutorService: Alle aktiven Tasks erhalten mit interrupt().ExecutorService-Frameworkpool-1-thread-1
pool-1-thread-3
pool-1-thread-2
pool-1-thread-4
pool-1-thread-5
pool-1-thread-3
pool-1-thread-4
pool-1-thread-3
pool-1-thread-2
pool-1-thread-4
pool-1-thread-3
pool-1-thread-5
pool-1-thread-3
pool-1-thread-4
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-4
Executors Factoryerzeugt bei Bedarf neue Threads, unbenutzte Threads werden nach 60 Sekunden beendet
\(\to\) Programme mit kurzlebigen, asynchronen Aufgaben
Executors Factorygenau nThreads werden erzeugt, überzählige Runnables bzw. Callables werden in Queue gespeichert
\(\to\) Programme mit sehr vielen unabhängigen Aufgaben
Executors FactorySonderfall von newFixedThreadPool(1). Stürzt der Thread ab, wird er neu gestartet
\(\to\) Programme mit weniger unabhängigen Aufgaben; “Absturzsicherung”
Executors FactoryAufgaben werden nach einer gegebenen Verzögerung bzw. periodisch ausgeführt
\(\to\) Programme mit vielen zeitlogisch abhängigen Aufgaben
Executors FactorySonderfall von newScheduledThreadPool(1). Stürzt der Thread ab, wird er neu gestartet
\(\to\) Programme mit einigen zeitlogisch abhängigen Aufgaben; “Absturzsicherung”
ScheduledExecutorServiceScheduledExecutorService bzw. dessen Implementierung ScheduledThreadPoolExecutor erlaubt Aufgaben in Form von Callable und Runnable
ExecutorService wird durch die Factory Executor erzeugt.ScheduledExecutorServiceDazu gibt es die Methoden:
schedulescheduleAtFixedRatescheduleWithFixedDelayScheduledExecutorServicevar scheduler = Executors.newScheduledThreadPool(1);
var beeperHandle = scheduler.scheduleAtFixedRate(
() -> System.out.println("beep"), 3, 3, TimeUnit.SECONDS
);
scheduler.schedule(
() -> beeperHandle.cancel(true), 5 * 3, TimeUnit.SECONDS
);
scheduler.schedule(
() -> System.exit(0), (5 * 3) + 5, TimeUnit.SECONDS
);pp.04.03-ThreadPoolSize\[\begin{eqnarray} N_{CPU} &=& Anzahl~der~CPUs\\ &=& \mathtt{Runtime.getRuntime().availableProcessors()}\\ U_{CPU} &=& CPU~Auslastung~~~(0 < U_{CPU} \le 1)\\ \frac{W}{C} &=& Verh.~zwischen~Wartezeit~und~Rechenzeit\\ N_{Threads} &=& N_{CPU} \times U_{CPU} \times (1 + \frac{W}{C}) \end{eqnarray}\]
bei rechenintensiven Tasks (nie im BLOCKED-, WAITING- oder TIMED_WAITING-Zustand -> \(\frac{W}{C}=0\)) auf einem halb ausgelasteten System (\(U_{CPU}=\frac12\)):
\[\begin{eqnarray} N_{Threads} &=& N_{CPU} \times \frac12 \times (1 + 0) = N_{CPU}\\ &=& \mathtt{Runtime.getRuntime().availableProcessors()/2} \end{eqnarray}\]
void execute(Runnable r)
r.run() auftretende unbehandlete Exceptions werden sofort geworfen.Future<T> submit(Callable<T> c) \(\to\) f
c.call() auftretende unbehandelte Exceptions werden “aufgehoben” und erst von f.get() geworfen.c.call() behandelt werden (z.B. zum Aufräumen), kann sie danach im catch-Block erneut geworfen werden (mit throw), um sie auch dem Aufrufer von f.get() weiterzuleiten.CompletionService für voneinander unabhängige Tasks1var pool = Executors.newCachedThreadPool();
var tasks = new ArrayList<Callable<String>>();
tasks.add(() -> "calc c1");
tasks.add(() -> "calc c2");
tasks.add(() -> "calc c3");
var completionService = new ExecutorCompletionService<String>(pool);
for (var callableTask : tasks) {
completionService.submit(callableTask);
}
try {
for (var i = 0; i < tasks.size(); i++) {
var future = completionService.take();
System.out.printf("Result %2d: %s\n", i, future.get());
}
} catch (InterruptedException | ExecutionException e) {
// ...
}
pool.shutdown();