Java Language
Utilizzo di ThreadPoolExecutor in applicazioni MultiThreaded.
Ricerca…
introduzione
Quando si crea un'applicazione performante e guidata dai dati, può essere molto utile eseguire attività che richiedono molto tempo in modo asincrono e far eseguire contemporaneamente più attività. Questo argomento introdurrà il concetto di utilizzo di ThreadPoolExecutors per completare contemporaneamente più attività asincrone.
Esecuzione di attività asincrone in cui non è necessario alcun valore di ritorno utilizzando un'istanza di classe eseguibile
Alcune applicazioni potrebbero voler creare attività cosiddette "Fire & Forget" che possono essere attivate periodicamente e non devono restituire alcun tipo di valore restituito al completamento dell'attività assegnata (ad esempio, eliminazione di vecchi file temporanei, registri in rotazione, salvataggio automatico stato).
In questo esempio, creeremo due classi: una che implementa l'interfaccia Runnable e una che contiene un metodo main ().
AsyncMaintenanceTaskCompleter.java
import lombok.extern.java.Log;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@Log
public class AsyncMaintenanceTaskCompleter implements Runnable {
private int taskNumber;
public AsyncMaintenanceTaskCompleter(int taskNumber) {
this.taskNumber = taskNumber;
}
public void run() {
int timeout = ThreadLocalRandom.current().nextInt(1, 20);
try {
log.info(String.format("Task %d is sleeping for %d seconds", taskNumber, timeout));
TimeUnit.SECONDS.sleep(timeout);
log.info(String.format("Task %d is done sleeping", taskNumber));
} catch (InterruptedException e) {
log.warning(e.getMessage());
}
}
}
AsyncExample1
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AsyncExample1 {
public static void main(String[] args){
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i = 0; i < 10; i++){
executorService.execute(new AsyncMaintenanceTaskCompleter(i));
}
executorService.shutdown();
}
}
L'esecuzione di AsyncExample1.main () ha prodotto il seguente output:
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 8 is sleeping for 18 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 6 is sleeping for 4 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 2 is sleeping for 6 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 3 is sleeping for 4 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 9 is sleeping for 14 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 4 is sleeping for 9 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 5 is sleeping for 10 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 0 is sleeping for 7 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 1 is sleeping for 9 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 7 is sleeping for 8 seconds
Dec 28, 2016 2:21:07 PM AsyncMaintenanceTaskCompleter run
INFO: Task 6 is done sleeping
Dec 28, 2016 2:21:07 PM AsyncMaintenanceTaskCompleter run
INFO: Task 3 is done sleeping
Dec 28, 2016 2:21:09 PM AsyncMaintenanceTaskCompleter run
INFO: Task 2 is done sleeping
Dec 28, 2016 2:21:10 PM AsyncMaintenanceTaskCompleter run
INFO: Task 0 is done sleeping
Dec 28, 2016 2:21:11 PM AsyncMaintenanceTaskCompleter run
INFO: Task 7 is done sleeping
Dec 28, 2016 2:21:12 PM AsyncMaintenanceTaskCompleter run
INFO: Task 4 is done sleeping
Dec 28, 2016 2:21:12 PM AsyncMaintenanceTaskCompleter run
INFO: Task 1 is done sleeping
Dec 28, 2016 2:21:13 PM AsyncMaintenanceTaskCompleter run
INFO: Task 5 is done sleeping
Dec 28, 2016 2:21:17 PM AsyncMaintenanceTaskCompleter run
INFO: Task 9 is done sleeping
Dec 28, 2016 2:21:21 PM AsyncMaintenanceTaskCompleter run
INFO: Task 8 is done sleeping
Process finished with exit code 0
Osservazioni sulla nota: ci sono diverse cose da notare nell'output sopra,
- Le attività non sono state eseguite in un ordine prevedibile.
- Dato che ogni attività stava dormendo per una quantità (pseudo) casuale di tempo, non necessariamente completavano nell'ordine in cui erano invocati.
Esecuzione di attività asincrone in cui è necessario un valore restituito utilizzando un'istanza di classe richiamabile
Spesso è necessario eseguire un'attività di lunga durata e utilizzare il risultato di tale attività una volta completata.
In questo esempio, creeremo due classi: Una che implementa l'interfaccia Callable <T> (dove T è il tipo che si desidera restituire) e una che contiene un metodo main ().
AsyncValueTypeTaskCompleter.java
import lombok.extern.java.Log;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@Log
public class AsyncValueTypeTaskCompleter implements Callable<Integer> {
private int taskNumber;
public AsyncValueTypeTaskCompleter(int taskNumber) {
this.taskNumber = taskNumber;
}
@Override
public Integer call() throws Exception {
int timeout = ThreadLocalRandom.current().nextInt(1, 20);
try {
log.info(String.format("Task %d is sleeping", taskNumber));
TimeUnit.SECONDS.sleep(timeout);
log.info(String.format("Task %d is done sleeping", taskNumber));
} catch (InterruptedException e) {
log.warning(e.getMessage());
}
return timeout;
}
}
AsyncExample2.java
import lombok.extern.java.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@Log
public class AsyncExample2 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
List<Future<Integer>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++){
Future<Integer> submittedFuture = executorService.submit(new AsyncValueTypeTaskCompleter(i));
futures.add(submittedFuture);
}
executorService.shutdown();
while(!futures.isEmpty()){
for(int j = 0; j < futures.size(); j++){
Future<Integer> f = futures.get(j);
if(f.isDone()){
try {
int timeout = f.get();
log.info(String.format("A task just completed after sleeping for %d seconds", timeout));
futures.remove(f);
} catch (InterruptedException | ExecutionException e) {
log.warning(e.getMessage());
}
}
}
}
}
}
L'esecuzione di AsyncExample2.main () ha prodotto il seguente output:
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 7 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 8 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 2 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 1 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 4 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 9 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 0 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 6 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 5 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 3 is sleeping
Dec 28, 2016 3:07:16 PM AsyncValueTypeTaskCompleter call
INFO: Task 8 is done sleeping
Dec 28, 2016 3:07:16 PM AsyncExample2 main
INFO: A task just completed after sleeping for 1 seconds
Dec 28, 2016 3:07:17 PM AsyncValueTypeTaskCompleter call
INFO: Task 2 is done sleeping
Dec 28, 2016 3:07:17 PM AsyncExample2 main
INFO: A task just completed after sleeping for 2 seconds
Dec 28, 2016 3:07:17 PM AsyncValueTypeTaskCompleter call
INFO: Task 9 is done sleeping
Dec 28, 2016 3:07:17 PM AsyncExample2 main
INFO: A task just completed after sleeping for 2 seconds
Dec 28, 2016 3:07:19 PM AsyncValueTypeTaskCompleter call
INFO: Task 3 is done sleeping
Dec 28, 2016 3:07:19 PM AsyncExample2 main
INFO: A task just completed after sleeping for 4 seconds
Dec 28, 2016 3:07:20 PM AsyncValueTypeTaskCompleter call
INFO: Task 0 is done sleeping
Dec 28, 2016 3:07:20 PM AsyncExample2 main
INFO: A task just completed after sleeping for 5 seconds
Dec 28, 2016 3:07:21 PM AsyncValueTypeTaskCompleter call
INFO: Task 5 is done sleeping
Dec 28, 2016 3:07:21 PM AsyncExample2 main
INFO: A task just completed after sleeping for 6 seconds
Dec 28, 2016 3:07:25 PM AsyncValueTypeTaskCompleter call
INFO: Task 1 is done sleeping
Dec 28, 2016 3:07:25 PM AsyncExample2 main
INFO: A task just completed after sleeping for 10 seconds
Dec 28, 2016 3:07:27 PM AsyncValueTypeTaskCompleter call
INFO: Task 6 is done sleeping
Dec 28, 2016 3:07:27 PM AsyncExample2 main
INFO: A task just completed after sleeping for 12 seconds
Dec 28, 2016 3:07:29 PM AsyncValueTypeTaskCompleter call
INFO: Task 7 is done sleeping
Dec 28, 2016 3:07:29 PM AsyncExample2 main
INFO: A task just completed after sleeping for 14 seconds
Dec 28, 2016 3:07:31 PM AsyncValueTypeTaskCompleter call
INFO: Task 4 is done sleeping
Dec 28, 2016 3:07:31 PM AsyncExample2 main
INFO: A task just completed after sleeping for 16 seconds
Osservazioni di nota:
Ci sono diverse cose da notare nell'output sopra,
- Ogni chiamata a ExecutorService.submit () ha restituito un'istanza di Future, che è stata memorizzata in un elenco per un uso futuro
- Future contiene un metodo chiamato isDone () che può essere usato per verificare se la nostra attività è stata completata prima di provare a controllare il suo valore di ritorno. Chiamare il metodo Future.get () su un Future che non è stato ancora eseguito bloccherà il thread corrente fino al completamento dell'attività, annullando potenzialmente molti benefici ottenuti dall'esecuzione dell'attività in modo asincrono.
- Il metodo executorService.shutdown () è stato chiamato prima di controllare i valori di ritorno degli oggetti Future. Questo non è richiesto, ma è stato fatto in questo modo per dimostrare che è possibile. Il metodo executorService.shutdown () non impedisce il completamento delle attività che sono già state inoltrate a ExecutorService, ma impedisce che nuove attività vengano aggiunte alla coda.
Definizione delle attività asincrone in linea utilizzando Lambdas
Mentre una buona progettazione del software spesso massimizza la riusabilità del codice, a volte può essere utile definire attività asincrone in linea nel codice tramite espressioni Lambda per massimizzare la leggibilità del codice.
In questo esempio, creeremo una singola classe che contiene un metodo main (). All'interno di questo metodo, utilizzeremo espressioni Lambda per creare ed eseguire istanze di Callable e Runnable <T>.
AsyncExample3.java
import lombok.extern.java.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
@Log
public class AsyncExample3 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
List<Future<Integer>> futures = new ArrayList<>();
for(int i = 0; i < 5; i++){
final int index = i;
executorService.execute(() -> {
int timeout = getTimeout();
log.info(String.format("Runnable %d has been submitted and will sleep for %d seconds", index, timeout));
try {
TimeUnit.SECONDS.sleep(timeout);
} catch (InterruptedException e) {
log.warning(e.getMessage());
}
log.info(String.format("Runnable %d has finished sleeping", index));
});
Future<Integer> submittedFuture = executorService.submit(() -> {
int timeout = getTimeout();
log.info(String.format("Callable %d will begin sleeping", index));
try {
TimeUnit.SECONDS.sleep(timeout);
} catch (InterruptedException e) {
log.warning(e.getMessage());
}
log.info(String.format("Callable %d is done sleeping", index));
return timeout;
});
futures.add(submittedFuture);
}
executorService.shutdown();
while(!futures.isEmpty()){
for(int j = 0; j < futures.size(); j++){
Future<Integer> f = futures.get(j);
if(f.isDone()){
try {
int timeout = f.get();
log.info(String.format("A task just completed after sleeping for %d seconds", timeout));
futures.remove(f);
} catch (InterruptedException | ExecutionException e) {
log.warning(e.getMessage());
}
}
}
}
}
public static int getTimeout(){
return ThreadLocalRandom.current().nextInt(1, 20);
}
}
Osservazioni di nota:
Ci sono diverse cose da notare nell'output sopra,
- Le espressioni Lambda hanno accesso a variabili e metodi disponibili per l'ambito in cui sono definiti, ma tutte le variabili devono essere definitive (o effettivamente definitive) per l'uso all'interno di un'espressione lambda.
- Non è necessario specificare se la nostra espressione Lambda è un riscattabile o una <T> eseguibile in modo esplicito, il tipo restituito viene inferito automaticamente dal tipo restituito.