Come chiamare in modo asincrono un metodo in Java


127

Ultimamente ho guardato le goroutine di Go e ho pensato che sarebbe stato bello avere qualcosa di simile in Java. Per quanto ho cercato, il modo comune per parallelizzare una chiamata al metodo è fare qualcosa come:

final String x = "somethingelse";
new Thread(new Runnable() {
           public void run() {
                x.matches("something");             
    }
}).start();

Non è molto elegante. C'è un modo migliore per farlo? Avevo bisogno di una tale soluzione in un progetto, quindi ho deciso di implementare la mia classe wrapper attorno a una chiamata al metodo asincrono.

Ho pubblicato la mia classe wrapper in J-Go . Ma non so se sia una buona soluzione. L'utilizzo è semplice:

SampleClass obj = ...
FutureResult<Integer> res = ...
Go go = new Go(obj);
go.callLater(res, "intReturningMethod", 10);         //10 is a Integer method parameter
//... Do something else
//...
System.out.println("Result: "+res.get());           //Blocks until intReturningMethod returns

o meno prolisso:

Go.with(obj).callLater("myRandomMethod");
//... Go away
if (Go.lastResult().isReady())                //Blocks until myRandomMethod has ended
    System.out.println("Method is finished!");

Internamente sto usando una classe che implementa Runnable e faccio un po 'di lavoro di riflessione per ottenere l'oggetto del metodo corretto e invocarlo.

Voglio qualche opinione sulla mia minuscola libreria e sull'argomento di effettuare chiamate di metodi asincroni come questa in Java. È sicuro? Esiste già un modo più semplice?


1
Puoi mostrare di nuovo il tuo codice di J-Go lib?
msangel

Stavo lavorando a un progetto in cui leggevo un flusso di caratteri. Una volta letta una parola completa, stavo eseguendo molte operazioni su quella parola. Alla fine l'ho messo in una collezione. Una volta letti tutti i dati dal flusso, restituisco la risposta. Ho deciso di avviare il thread ogni volta che eseguo un'operazione su una parola. Ma ha diminuito le prestazioni complessive. Poi ho capito che i thread sono di per sé un'operazione costosa. Non sono sicuro se l'avvio di un thread per chiamare un metodo contemporaneamente possa fornire prestazioni aggiuntive fino a quando non esegue un'operazione di I / O pesante.
Amit Kumar Gupta

2
Ottima domanda !! Java 8 fornisce CompletableFutures per questo. Altre risposte si basano su versioni probabilmente vecchie di Java
TriCore

Risposte:


155

Ho appena scoperto che esiste un modo più pulito per fare il tuo

new Thread(new Runnable() {
    public void run() {
        //Do whatever
    }
}).start();

(Almeno in Java 8), puoi utilizzare un'espressione lambda per abbreviarla in:

new Thread(() -> {
    //Do whatever
}).start();

Semplice come creare una funzione in JS!


La tua risposta ha aiutato il mio problema: stackoverflow.com/questions/27009448/… . Un po 'complicato da applicare la mia situazione, ma alla fine ha risolto il problema :)
Deckard

Come chiamare con parametri?
yatanadam

2
@yatanadam Questo potrebbe rispondere alla tua domanda. Basta inserire il codice sopra all'interno di un metodo e passare la variabile come faresti normalmente. Dai un'occhiata a questo codice di prova che ho creato per te.
user3004449

1
@eNnillaMS Il thread deve essere interrotto dopo l'esecuzione? Si ferma automaticamente o dal garbage collector ?
user3004449

@ user3004449 Il thread si interrompe automaticamente, tuttavia puoi anche forzarlo.
Shawn

59

Java 8 ha introdotto CompletableFuture disponibile nel pacchetto java.util.concurrent.CompletableFuture, può essere utilizzato per effettuare una chiamata asincrona:

CompletableFuture.runAsync(() -> {
    // method call or code to be asynch.
});

CompletableFutureè stato menzionato in un'altra risposta, ma quella utilizzava l'intera supplyAsync(...)catena. Questo è un semplice involucro che si adatta perfettamente alla domanda.
ndm13

ForkJoinPool.commonPool().execute()ha un sovraccarico leggermente inferiore
Mark Jeronimus

31

Potresti considerare anche la classe java.util.concurrent.FutureTask.

Se utilizzi Java 5 o versioni successive, FutureTask è un'implementazione chiavi in ​​mano di "Un calcolo asincrono cancellabile".

Nel java.util.concurrentpacchetto sono disponibili comportamenti di pianificazione dell'esecuzione asincrona ancora più ricchi (ad esempio, ScheduledExecutorService), ma FutureTaskpotrebbero avere tutte le funzionalità necessarie.

Mi spingerei addirittura a dire che non è più consigliabile utilizzare il primo modello di codice che hai fornito come esempio da quando è FutureTaskdiventato disponibile. (Supponendo che tu sia su Java 5 o successivo.)


Il fatto è che voglio solo eseguire una chiamata al metodo. In questo modo dovrei cambiare l'implementazione della classe di destinazione. La cosa che volevo era esattamente chiamare senza doversi preoccupare di impĺementare Runnable o Callable
Felipe Hummel

Ti sento. Java non ha (ancora) funzioni di prima classe, quindi questo è lo stato dell'arte in questo momento.
ombre il

grazie per le parole chiave 'future' ... ora apro i tutorial su di loro ... molto utili. : D
gumuruh

4
-1 per quanto ne so, FutureTask da solo non è sufficiente per eseguire nulla in modo asincrono. È ancora necessario creare un thread o un esecutore per eseguirlo, come nell'esempio di Carlos.
MikeFHay

24

Non mi piace l'idea di usare Reflection per questo.
Non solo pericoloso per averlo perso in qualche refactoring, ma può anche essere negato SecurityManager.

FutureTaskè una buona opzione come le altre opzioni del pacchetto java.util.concurrent.
Il mio preferito per compiti semplici:

    Executors.newSingleThreadExecutor().submit(task);

un po 'più breve della creazione di un thread (l'attività è un chiamabile o eseguibile)


1
Il fatto è che voglio solo eseguire una chiamata al metodo. In questo modo dovrei cambiare l'implementazione della classe di destinazione. La cosa che volevo era esattamente chiamare senza doversi preoccupare di impĺementare Runnable o Callable
Felipe Hummel

quindi questo non sarebbe di grande aiuto :( ma normalmente preferirei utilizzare un Runnable o Callable invece di Reflection
user85421

4
Solo una breve nota: è meglio tenere traccia ExecutorServicedell'istanza, in modo da poter chiamare shutdown()quando è necessario.
Daniel Szalay

1
Se lo facessi, non ti ritroveresti con ExecutorService non chiuso, facendo sì che la tua JVM si rifiuti di spegnersi? Consiglierei di scrivere il tuo metodo per ottenere un ExecutorService a thread singolo e limitato nel tempo.
Djangofan

14

È possibile utilizzare la sintassi Java8 per CompletableFuture, in questo modo è possibile eseguire calcoli asincroni aggiuntivi basati sul risultato della chiamata di una funzione asincrona.

per esempio:

 CompletableFuture.supplyAsync(this::findSomeData)
                     .thenApply(this:: intReturningMethod)
                     .thenAccept(this::notify);

Maggiori dettagli possono essere trovati in questo articolo


10

È possibile utilizzare l' @Asyncannotazione da jcabi- effects e AspectJ:

public class Foo {
  @Async
  public void save() {
    // to be executed in the background
  }
}

Quando chiami save(), un nuovo thread inizia ed esegue il suo corpo. Il tuo thread principale continua senza attendere il risultato di save().


1
Si noti che questo approccio renderebbe tutte le chiamate per il salvataggio asincrono, il che potrebbe essere o meno quello che vogliamo. Nei casi in cui solo alcune chiamate a un determinato metodo dovrebbero essere asincrone, è possibile utilizzare altri approcci suggeriti in questa pagina.
bmorin

Posso usarlo in un'applicazione web (poiché la gestione dei thread non è consigliata lì)?
onlinenaman

8

Puoi usare Future-AsyncResult per questo.

@Async
public Future<Page> findPage(String page) throws InterruptedException {
    System.out.println("Looking up " + page);
    Page results = restTemplate.getForObject("http://graph.facebook.com/" + page, Page.class);
    Thread.sleep(1000L);
    return new AsyncResult<Page>(results);
}

Riferimento: https://spring.io/guides/gs/async-method/


10
se stai usando lo spring-boot.
Giovanni Toraldo

6

Java fornisce anche un bel modo per chiamare metodi asincroni. in java.util.concurrent abbiamo ExecutorService che aiuta a fare lo stesso. Inizializza il tuo oggetto in questo modo:

 private ExecutorService asyncExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

e quindi chiama la funzione come-

asyncExecutor.execute(() -> {

                        TimeUnit.SECONDS.sleep(3L);}

3

Probabilmente non è una soluzione reale, ma ora, in Java 8, puoi rendere questo codice almeno un po 'migliore usando l'espressione lambda.

final String x = "somethingelse";
new Thread(() -> {
        x.matches("something");             
    }
).start();

E potresti anche farlo in una riga, comunque abbastanza leggibile.

new Thread(() -> x.matches("something")).start();

È davvero bello usarlo usando Java 8.
Mukti

3

Puoi usare AsyncFuncda Cactoos :

boolean matches = new AsyncFunc(
  x -> x.matches("something")
).apply("The text").get();

Sarà eseguito in background e il risultato sarà disponibile in get()come Future.


2

Questo non è realmente correlato, ma se dovessi chiamare in modo asincrono un metodo, ad esempio match (), userei:

private final static ExecutorService service = Executors.newFixedThreadPool(10);
public static Future<Boolean> matches(final String x, final String y) {
    return service.submit(new Callable<Boolean>() {

        @Override
        public Boolean call() throws Exception {
            return x.matches(y);
        }

    });
}

Quindi per chiamare il metodo asincrono userei:

String x = "somethingelse";
try {
    System.out.println("Matches: "+matches(x, "something").get());
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

L'ho provato e funziona. Ho solo pensato che potrebbe aiutare gli altri se venissero solo per il "metodo asincrono".


1

C'è anche una bella libreria per Async-Await creata da EA: https://github.com/electronicarts/ea-async

Dal loro file Leggimi:

Con EA Async

import static com.ea.async.Async.await;
import static java.util.concurrent.CompletableFuture.completedFuture;

public class Store
{
    public CompletableFuture<Boolean> buyItem(String itemTypeId, int cost)
    {
        if(!await(bank.decrement(cost))) {
            return completedFuture(false);
        }
        await(inventory.giveItem(itemTypeId));
        return completedFuture(true);
    }
}

Senza EA Async

import static java.util.concurrent.CompletableFuture.completedFuture;

public class Store
{
    public CompletableFuture<Boolean> buyItem(String itemTypeId, int cost)
    {
        return bank.decrement(cost)
            .thenCompose(result -> {
                if(!result) {
                    return completedFuture(false);
                }
                return inventory.giveItem(itemTypeId).thenApply(res -> true);
            });
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.