Runnable vs Callable in Java

1. Panoramica

Sin dagli albori di Java, il multithreading è stato un aspetto importante del linguaggio. Runnable è l'interfaccia principale fornita per rappresentare le attività multi-thread e Callable è una versione migliorata di Runnable aggiunta in Java 1.5.

In questo articolo esploreremo le differenze e le applicazioni di entrambe le interfacce.

2. Meccanismo di esecuzione

Entrambe le interfacce sono progettate per rappresentare un'attività che può essere eseguita da più thread. Le attività eseguibili possono essere eseguite utilizzando la classe Thread o ExecutorService mentre Callables può essere eseguita solo utilizzando quest'ultima.

3. Valori restituiti

Diamo uno sguardo più approfondito al modo in cui queste interfacce gestiscono i valori di ritorno.

3.1. Con Runnable

L' interfaccia Runnable è un'interfaccia funzionale e ha un unico metodo run () che non accetta alcun parametro e non restituisce alcun valore.

Questo è adatto per situazioni in cui non stiamo cercando un risultato dell'esecuzione del thread, ad esempio, la registrazione degli eventi in arrivo:

public interface Runnable { public void run(); }

Capiamolo con un esempio:

public class EventLoggingTask implements Runnable{ private Logger logger = LoggerFactory.getLogger(EventLoggingTask.class); @Override public void run() { logger.info("Message"); } }

In questo esempio, il thread leggerà semplicemente un messaggio dalla coda e lo registrerà in un file di registro. Nessun valore restituito dall'attività; l'attività può essere avviata utilizzando ExecutorService:

public void executeTask() { executorService = Executors.newSingleThreadExecutor(); Future future = executorService.submit(new EventLoggingTask()); executorService.shutdown(); }

In questo caso, l' oggetto Future non manterrà alcun valore.

3.2. Con Callable

L' interfaccia Callable è un'interfaccia generica contenente un singolo metodo call () , che restituisce un valore generico V :

public interface Callable { V call() throws Exception; }

Diamo un'occhiata al calcolo del fattoriale di un numero:

public class FactorialTask implements Callable { int number; // standard constructors public Integer call() throws InvalidParamaterException { int fact = 1; // ... for(int count = number; count > 1; count--) { fact = fact * count; } return fact; } }

Il risultato del metodo call () viene restituito all'interno di un oggetto Future :

@Test public void whenTaskSubmitted_ThenFutureResultObtained(){ FactorialTask task = new FactorialTask(5); Future future = executorService.submit(task); assertEquals(120, future.get().intValue()); }

4. Gestione delle eccezioni

Vediamo quanto sono adatti per la gestione delle eccezioni.

4.1. Con Runnable

Poiché la firma del metodo non ha la clausola "throws" specificata,non c'è modo di propagare ulteriori eccezioni verificate.

4.2. Con Callable

Il metodo call () di Callable contiene la clausola "throws Exception" in modo che possiamo facilmente propagare ulteriormente le eccezioni verificate:

public class FactorialTask implements Callable { // ... public Integer call() throws InvalidParamaterException { if(number < 0) { throw new InvalidParamaterException("Number should be positive"); } // ... } }

In caso di esecuzione di un Callable utilizzando un ExecutorService, le eccezioni vengono raccolte nell'oggetto Future , che può essere verificato effettuando una chiamata al metodo Future.get () . Questo genererà un'ExecutionException, che avvolge l'eccezione originale:

@Test(expected = ExecutionException.class) public void whenException_ThenCallableThrowsIt() { FactorialCallableTask task = new FactorialCallableTask(-5); Future future = executorService.submit(task); Integer result = future.get().intValue(); }

Nel test precedente, viene lanciata ExecutionException mentre stiamo passando un numero non valido. Possiamo chiamare il metodo getCause () su questo oggetto eccezione per ottenere l'eccezione selezionata originale.

Se non effettuiamo la chiamata al metodo get () della classe Future , l'eccezione generata dal metodo call () non verrà restituita e l'attività sarà comunque contrassegnata come completata:

@Test public void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){ FactorialCallableTask task = new FactorialCallableTask(-5); Future future = executorService.submit(task); assertEquals(false, future.isDone()); }

Il test precedente passerà con successo anche se abbiamo lanciato un'eccezione per i valori negativi del parametro a FactorialCallableTask.

5. conclusione

In questo articolo, abbiamo esplorato le differenze tra le interfacce Runnable e Callable .

Come sempre, il codice completo di questo articolo è disponibile su GitHub.