2015-06-27 9 views
27

Leggere attentamente la domanda prima di contrassegnarla come duplicata.Chiamata di metodo ai blocchi Future.get(). È davvero desiderabile?

Di seguito è riportato lo snippet dello pseudo codice. La mia domanda è: il codice seguente non sconfigge la nozione stessa di elaborazione parallela asincrona?

Il motivo per cui lo chiedo è perché nel codice sottostante il thread principale dovrebbe inviare un'attività da eseguire in un thread diverso. Dopo aver inviato l'attività in coda, blocca il metodo Future.get() affinché l'attività restituisca il valore. Preferirei che l'attività fosse eseguita nella discussione principale anziché inviarla a un thread diverso e attendere i risultati. Che cosa ho ottenuto eseguendo l'attività in una nuova discussione?

Sono consapevole che è possibile attendere un tempo limitato, ecc., Ma che succede se mi interessa davvero del risultato? Il problema peggiora se ci sono più attività da eseguire. Mi sembra che stiamo semplicemente facendo il lavoro in modo sincrono. Sono a conoscenza della libreria Guava che fornisce un'interfaccia listener non bloccante. Ma sono interessato a sapere se la mia comprensione è corretta per l'API Future.get(). Se è corretto, perché il Future.get() è progettato per bloccare e quindi sconfiggere l'intero processo di elaborazione parallela?

Nota - Per la cronaca, io uso JAVA 6

public static void main(String[] args){ 

private ExectorService executorService = ... 

Future future = executorService.submit(new Callable(){ 
    public Object call() throws Exception { 
     System.out.println("Asynchronous Callable"); 
     return "Callable Result"; 
    } 
}); 

System.out.println("future.get() = " + future.get()); 
} 
+4

L'idea è che si dovrebbe presentare più attività e poi aspettare. Hai ragione che se aspetti il ​​risultato tra l'invio di ogni attività, allora saranno processati in serie e non otterrai nulla. –

+0

@SkinnyJ Se più attività sono sottoposte, come fai a sapere il risultato restituito è per il quale compito? E come aspetto più compiti? –

+1

@VishalP Si avrà quindi la lista dei Futures, che si potrebbe verificare con isDone() o fetch risultato con get() – John

risposta

28

Future vi offre il metodo isDone() che non blocca e restituisce vero se il calcolo è stato completato, altrimenti false.

Future.get() viene utilizzato per recuperare il risultato del calcolo.

Hai un paio di opzioni:

  • chiamata isDone() e se il risultato è pronto chiedere esso invocando get(), notare come non v'è alcun blocco
  • blocco a tempo indeterminato con get()
  • blocco per timeout specificato con get(long timeout, TimeUnit unit)

L'intera cosa Future API è lì per h come ottenere facilmente i valori dai thread che eseguono attività parallele. Questo può essere fatto in modo sincrono o asincrono se preferisci, come descritto nei punti sopra.

AGGIORNAMENTO CON CACHE ESEMPIO

Ecco un'implementazione della cache dalla Java Concurrency in Practice, un caso eccellente utilizzazione Future.

  • Se il calcolo è già in esecuzione, chiamante interessati a seguito di calcolo attenderà per il calcolo finisca
  • Se il risultato è pronto nella cache, chiamante lo ritirerà
  • se il risultato non è pronto e il calcolo non è ancora iniziato, il chiamante inizierà il calcolo e avvolgerà i risultati in Future per altri chiamanti.

Tutto ciò è facilmente raggiungibile con l'API Future.

package net.jcip.examples; 

import java.util.concurrent.*; 
/** 
* Memoizer 
* <p/> 
* Final implementation of Memoizer 
* 
* @author Brian Goetz and Tim Peierls 
*/ 
public class Memoizer <A, V> implements Computable<A, V> { 
    private final ConcurrentMap<A, Future<V>> cache 
      = new ConcurrentHashMap<A, Future<V>>(); 
    private final Computable<A, V> c; 

public Memoizer(Computable<A, V> c) { 
    this.c = c; 
} 

public V compute(final A arg) throws InterruptedException { 
    while (true) { 

     Future<V> f = cache.get(arg); 
     // computation not started 
     if (f == null) { 
      Callable<V> eval = new Callable<V>() { 
       public V call() throws InterruptedException { 
        return c.compute(arg); 
       } 
      }; 

      FutureTask<V> ft = new FutureTask<V>(eval); 
      f = cache.putIfAbsent(arg, ft); 
      // start computation if it's not started in the meantime 
      if (f == null) { 
       f = ft; 
       ft.run(); 
      } 
     } 

     // get result if ready, otherwise block and wait 
     try { 
      return f.get(); 
     } catch (CancellationException e) { 
      cache.remove(arg, f); 
     } catch (ExecutionException e) { 
      throw LaunderThrowable.launderThrowable(e.getCause()); 
     } 
    } 
    } 
} 
+2

Ma poi ancora aspettare in un ciclo controllo isDone(), non è questo blocco pure? –

+1

Dipende interamente dal tuo design. Non devi. Ad esempio, potresti avere un evento di distribuzione del thread non appena entrano nel sistema e controllare periodicamente il risultato. In questo scenario, il controllo dei risultati se non sono pronti non verrebbe bloccato e il thread potrebbe continuare con gli eventi di distribuzione. Ci sono molti modelli che potrebbero essere usati qui per evitare il blocco. Controlla il pattern Observer che può essere usato per notificare il risultato, anche Active Object Pattern e Half Sync - Half Async, Publish Subscribe. Prova a leggere un po 'su questi modelli per avere un'idea di come utilizzare la programmazione asincrona. – John

+1

Hai ragione. Ma mi chiedo ancora se questo è reso inutilmente complesso. Una soluzione ascoltatrice pronta all'uso sarebbe stata molto utile. –

1

Nell'esempio hai dato si potrebbe anche eseguire tutto nel metodo main() e andare il tuo modo allegro.

Ma supponiamo di avere tre fasi di calcolo che stai correntemente eseguendo in sequenza. Giusto per capire supponiamo che step1 impieghi t1 secondi, step2 impieghi t2 secondi e step3 impieghi t3 secondi per completarsi. Quindi il tempo totale di calcolo è t1+t2+t3. Supponiamo inoltre che t2>t1>=t3.

Consideriamo ora uno scenario quando eseguiamo questi tre passaggi in parallelo usando Future per contenere tutti i risultati di calcolo. È possibile verificare se ciascuna attività viene eseguita utilizzando la chiamata isDone() non bloccante sui future corrispondenti. Ora cosa succede? in teoria la tua esecuzione è veloce come quella che t2 completa correttamente? Quindi noi ha ottenuto alcuni vantaggi dal parallelismo.

Inoltre, in Java8, è disponibile il CompletableFuture che supporta i richiami di stile funzionale.

+1

Lasciami fare il mio esempio. Le attività che invio al servizio finiscono per sollevare richieste HTTP. Il risultato della richiesta HTTP può richiedere molto tempo. Ma ho bisogno del risultato di ogni richiesta HTTP. Le attività sono presentate in un ciclo. Se aspetto che ogni compito ritorni (prendi), allora sto perdendo il parallelismo qui, no? –

+0

No, invoca get solo quando isDone() restituisce 'true'. È meglio che eseguire tutte le chiamate in sequenza. Quindi, in sostanza, la tua esecuzione sarà solo veloce come il più lento servizio web esecuzione (compresa rete ecc) –

0

Se non si cura dei risultati, generare un nuovo thread e da tale thread utilizzare l'API ExectorService per l'invio dell'attività. In questo modo, il thread padre, cioè il thread main, non bloccherà in alcun modo, genererebbe semplicemente un nuovo thread e quindi avvierà un'ulteriore esecuzione, mentre il nuovo thread invierà le tue attività.

Per creare un nuovo thread: esegui da solo un ThreadFactory per la creazione di thread asincrona o utilizza un'implementazione di java.util.concurrent.Executor.

Se questo è in un'applicazione JEE e si utilizza il framework Spring, è possibile creare facilmente un nuovo thread asincrono utilizzando l'annotazione @async.

Spero che questo aiuti!

+0

@VishalP hanno ottenuto qualche risposta? – hagrawal

4

Di seguito è riportato lo snippet dello pseudo codice. La mia domanda è: il codice seguente non sconfigge la nozione stessa di elaborazione parallela asincrona?

Tutto dipende dal vostro caso d'uso:

  1. Se davvero si vuole bloccare fino ad ottenere il risultato, l'uso bloccando get()
  2. Se si può aspettare per un determinato periodo per conoscere lo stato invece di una durata di blocco infinita, utilizzare con timeout
  3. Se è possibile continuare senza analizzare immediatamente il risultato e ispezionare il risultato in futuro, utilizzare CompletableFuture

    Un Futuro che può essere completato in modo esplicito (impostandone il valore e lo stato) e può essere utilizzato come CompletionStage, supportando funzioni dipendenti e azioni che si attivano al suo completamento.

  4. È possibile implementare il meccanismo di callback da Runnable/Callable. Date un'occhiata al di sotto SE domanda:

    Java executors: how to be notified, without blocking, when a task completes?

+0

thnx @ravnidra. Allora non stavo usando 1.8, quindi CompletableFuture era fuori questione. Ma gli altri input sono piuttosto rilevanti. –