2016-04-15 14 views
7

Qual è il modo corretto di implementare la concorrenza nelle applicazioni Java? So di Thread e roba, ovviamente, ho programmato per Java da 10 anni, ma non ho avuto troppa esperienza con la concorrenza.Come aspettare il completamento di più attività in Java?

Ad esempio, devo caricare alcune risorse in modo asincrono, e solo dopo che sono state caricate, posso procedere e fare più lavoro. Inutile dire che non c'è ordine su come finiranno. Come faccio a fare questo?

In JavaScript, Mi piace usare l'infrastruttura jQuery.deferred, a dire

$.when(deferred1,deferred2,deferred3...) 
.done(
    function(){//here everything is done 
    ... 
    }); 

Ma cosa devo fare in Java?

+0

Quale versione di Java? –

+0

Dai un'occhiata a RxJava – tddmonkey

+0

Cerca i thread Java su google. Abbastanza facile da implementare la concorrenza in Java. –

risposta

2

Se io non sto usando flussi paralleli o TaskExecutor di Spring MVC, io di solito uso CountDownLatch. Crea un'istanza con # di attività, riduci una volta per ogni thread che completa la sua attività. CountDownLatch.await() attende fino a quando il latch è a 0. Molto utile.

Per saperne di più qui: JavaDocs

+0

Qual è la differenza tra questo e il Semaforo? –

+0

@ doom777 CountdownLatch viene utilizzato per avviare una serie di thread e quindi attendere che tutti siano completi (o finché non chiamano countDown() un determinato numero di volte). Il semaforo viene utilizzato per controllare il numero di thread simultanei che utilizzano una risorsa. Quella risorsa può essere qualcosa come un file, o potrebbe essere la cpu limitando il numero di thread in esecuzione. Il conteggio su un semaforo può andare su e giù mentre thread diversi chiamano acquire() e release(). –

+0

Sì, ma non è 'CountdownLatch' solo un semaforo che non può salire? –

3

Vorrei utilizzare flusso parallelo.

Stream.of(runnable1, runnable2, runnable3).parallel().forEach(r -> r.run()); 
// do something after all these are done. 

Se è necessario che sia asincrono, è possibile utilizzare un pool o Thread.

devo caricare in modo asincrono alcune risorse,

Si potrebbe raccogliere queste risorse come questo.

List<String> urls = .... 

Map<String, String> map = urls.parallelStream() 
           .collect(Collectors.toMap(u -> u, u -> download(u))); 

Questo ti darà una mappatura di tutte le risorse una volta che sono state scaricate contemporaneamente. La concorrenza sarà il numero di CPU che hai per impostazione predefinita.

+0

@RobinJonsson forEach non viene restituito finché tutte le attività non sono state completate. Non vi è alcun supporto per l'esecuzione del codice di flusso in modo asincrono, motivo per cui è necessario fare qualcos'altro se necessario. –

+1

Vero! Mi sono confuso pensando che "t" fosse un filo. "t" è forse un runnable ... –

+0

@RobinJonsson Grazie, penso che sia più chiaro ora. –

0

Io di solito optare per un asincrona avvisare-start, notify-progresso, notify-end approccio:

class Task extends Thread { 
    private ThreadLauncher parent; 

    public Task(ThreadLauncher parent) { 
     super(); 
     this.parent = parent; 
    } 

    public void run() { 
     doStuff(); 

     parent.notifyEnd(this); 
    } 

    public /*abstract*/ void doStuff() { 
     // ... 
    } 
} 


class ThreadLauncher { 

    public void stuff() { 
     for (int i=0; i<10; i++) 
      new Task(this).start(); 
    } 

    public void notifyEnd(Task who) { 
     // ... 
    } 
} 
+2

Suggerisco di non estendere direttamente Thread. –

+1

sì, è possibile implementare Runnable se si desidera farlo – Exceptyon

1

Questo è un esempio che uso discussioni. È un executerService statico con una dimensione fissa di 50 thread.

public class ThreadPoolExecutor { 

private static final ExecutorService executorService = Executors.newFixedThreadPool(50, 
     new ThreadFactoryBuilder().setNameFormat("thread-%d").build()); 

private static ThreadPoolExecutor instance = new ThreadPoolExecutor(); 

public static ThreadPoolExecutor getInstance() { 
    return instance; 
} 

public <T> Future<? extends T> queueJob(Callable<? extends T> task) { 
    return executorService.submit(task); 
} 

public void shutdown() { 
    executorService.shutdown(); 
} 
} 

La logica dell'attività per l'esecutore viene usato in questo modo: (È possibile utilizzare Callable o Runnable Callable può restituire qualcosa, Runnable no.)

public class MultipleExecutor implements Callable<ReturnType> {//your code} 

e la chiamata del esecutore:

ThreadPoolExecutor threadPoolExecutor = ThreadPoolExecutor.getInstance(); 

List<Future<? extends ReturnType>> results = new LinkedList<>(); 

for (Type Type : typeList) { 
      Future<? extends ReturnType> future = threadPoolExecutor.queueJob(
        new MultipleExecutor(needed parameters)); 
      results.add(future); 
     } 

     for (Future<? extends ReturnType> result : results) { 
      try { 
       if (result.get() != null) { 
        result.get(); // here you get the return of one thread 
       } 
      } catch (InterruptedException | ExecutionException e) { 
       logger.error(e, e); 
      } 
     } 
4

È possibile realizzarlo in diversi modi.

1.ExecutorServiceinvokeAll() API

esegue i compiti assegnati, restituendo una lista di Futures tenendo il loro stato e dei risultati quando tutto completi.

2. CountDownLatch

Un aiuto sincronizzazione che consente uno o più fili di attendere una serie di operazioni eseguite in altri thread completa.

A CountDownLatch è inizializzato con un determinato conteggio. Il blocco attendi i metodi fino a quando il conteggio corrente non raggiunge lo zero a causa di invocazioni del metodo countDown(), dopo il quale tutti i thread in attesa vengono rilasciati e qualsiasi successiva chiamata di attesa attende immediatamente. Questo è un fenomeno one-shot: il conteggio non può essere resettato. Se è necessaria una versione che azzeri il conteggio, prendere in considerazione l'uso di CyclicBarrier.

3. ForkJoinPool o newWorkStealingPool() in Executors è altro modo

Date un'occhiata a domande SE correlati:

How to wait for a thread that spawns it's own thread?

Executors: How to synchronously wait until all tasks have finished if tasks are created recursively?

+0

Ottima risposta. Vorrei anche aggiungere CyclicBarrier - ha un costruttore, che accetta un Runnable, che verrà eseguito quando tutte le attività sono finite: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent /CyclicBarrier.html#CyclicBarrier-int-java.lang.Runnable- –

+0

Qual è il modo corretto di fare la stessa cosa, ma senza bloccare alcun thread? –

1

lo stesso comportamento con $.Deferred in jQuery puoi archiviare in Java 8 con a classe chiamata CompletableFuture. Questa classe fornisce l'API per lavorare con Promises. Per creare un codice asincrono, puoi utilizzare uno di questi metodi di creazione static come #runAsync, #supplyAsync. Quindi applicare alcuni calcoli dei risultati con #thenApply.