Dobavljanje rezultata izvršenja Java niti: Callable, Future i FutureTask
U prvoj lekciji: osnove Java višenitnosti opisali smo 3 načina kreiranja niti: direktno nasleđivanje klase Thread, implementacija interfejsa Runnable i implementacija interfejsa Callable.
Prva dva načina imaju jedan nedostatak: nakon izvršenja zadatka ne mogu se dobiti rezultati izvršenja.
Ako želimo da dobavimo rezultat izvršenja, moramo se poslužiti deljenim promenljivama ili komunikacijom među nitima, što je nezgodno za upotrebu.
Java 1.5 pruža Callable, Future i FutureTask koji nakon izvršenja zadatka mogu dobiti rezultat; danas ćemo ih detaljno upoznati.
Runnable bez povratne vrednosti
Pošto metod run() interfejsa Runnable vraća void:
public interface Runnable {
public abstract void run();
}ne može vratiti nikakav rezultat nakon izvršenja zadatka.
Callable sa povratnom vrednošću
Callable se nalazi u paketu java.util.concurrent, takođe je interfejs i definiše metod call():
public interface Callable<V> {
V call() throws Exception;
}Vidimo da call() vraća vrednost tipa V — generičkog tipa.
Kako koristiti Callable?
Obično se koristi uz ExecutorService (koji ćemo detaljno obraditi kada budemo govorili o bazenima niti; za sada samo zapamtite).
ExecutorService je interfejs u paketu java.util.concurrent; jezgro je okvira Java bazena niti, služi za asinhrono izvršavanje zadataka i pruža neke ključne metode za upravljanje nitima.

Sledeći primer koristi metod submit interfejsa ExecutorService.
// Kreira bazen niti sa 5 niti
ExecutorService executorService = Executors.newFixedThreadPool(5);
// Kreira Callable zadatak
Callable<String> task = new Callable<String>() {
public String call() {
return "Hello from " + Thread.currentThread().getName();
}
};
// Podnosi zadatak ExecutorService-u na izvršenje i dobavlja Future objekat
Future[] futures = new Future[10];
for (int i = 0; i < 10; i++) {
futures[i] = executorService.submit(task);
}
// Preko Future objekta dobavlja rezultat zadatka
for (int i = 0; i < 10; i++) {
System.out.println(futures[i].get());
}
// Zatvara ExecutorService, ne prihvata nove zadatke, čeka da se završe svi podneti zadaci
executorService.shutdown();Preko alatke Executors kreiramo ExecutorService, zatim mu podnosimo Callable zadatak, a zatim preko Future-a dobavljamo rezultat izvršenja.
Radi poređenja, pogledajmo i način sa Runnable:
// Kreira bazen niti sa 5 niti
ExecutorService executorService = Executors.newFixedThreadPool(5);
// Kreira Runnable zadatak
Runnable task = new Runnable() {
public void run() {
System.out.println("Hello from " + Thread.currentThread().getName());
}
};
// Podnosi zadatak ExecutorService-u na izvršenje
for (int i = 0; i < 10; i++) {
executorService.submit(task);
}
// Zatvara ExecutorService, ne prihvata nove zadatke, čeka da se završe svi podneti zadaci
executorService.shutdown();Vidimo da je način sa Runnable nešto jednostavniji od načina sa Callable, ali način sa Callable može dobiti rezultat izvršenja, što Runnable ne može.
Future — interfejs za rezultat asinhronog izračunavanja
U prethodnom primeru smo preko Future-a dobavljali rezultat izvršenja Callable zadatka. Šta je zapravo Future?
Future se nalazi u paketu java.util.concurrent i to je interfejs:
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}Ukupno deklariše 5 metoda:
cancel()služi za otkazivanje zadatka; ako je otkazivanje uspelo, vraća true, inače false. Parametar mayInterruptIfRunning označava da li je dozvoljeno otkazati zadatak u toku koji još nije završen; ako je postavljen na true, znači da se zadatak u izvršenju može otkazati. Ako je zadatak već završen, ovaj metod sigurno vraća false bez obzira na to da li je mayInterruptIfRunning true ili false — dakle otkazivanje već završenog zadatka vraća false; ako je zadatak u izvršenju, vraća true kada je mayInterruptIfRunning true, a false kada je false; ako zadatak još nije počeo, sigurno vraća true bez obzira na mayInterruptIfRunning.isCancelled()pokazuje da li je zadatak uspešno otkazan; ako je uspešno otkazan pre normalnog završetka, vraća true.isDone()pokazuje da li je zadatak završen; ako jeste, vraća true.get()služi za dobavljanje rezultata izvršenja; ovaj metod blokira i čeka dok se zadatak ne završi, pa tek onda vraća rezultat.get(long timeout, TimeUnit unit)služi za dobavljanje rezultata izvršenja; ako rezultat ne stigne u zadatom vremenu, vraća null.
Dakle, Future pruža tri funkcije:
- proveru da li je zadatak završen;
- mogućnost prekida zadatka;
- dobavljanje rezultata izvršenja zadatka.
Pošto je Future samo interfejs, pri direktnom instanciranju kompajler izdaje upozorenje i sugeriše nam da radije koristimo FutureTask.

U stvari, FutureTask je jedina implementaciona klasa interfejsa Future; u prethodnom primeru executorService.submit() vraća FutureTask, što možete videti u debug režimu.

FutureTask — implementaciona klasa za rezultat asinhronog izračunavanja
Pogledajmo implementaciju FutureTask-a:
public class FutureTask<V> implements RunnableFuture<V>Klasa FutureTask implementira interfejs RunnableFuture; pogledajmo RunnableFuture:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}Vidimo da RunnableFuture nasleđuje interfejse Runnable i Future, a da FutureTask implementira RunnableFuture. Dakle, FutureTask može biti izvršavan od strane niti kao Runnable, a takođe može i kao Future dobiti povratnu vrednost Callable-a.
FutureTask ima dva konstruktora:
public FutureTask(Callable<V> callable) {
}
public FutureTask(Runnable runnable, V result) {
}Kada je potrebno asinhrono izvršiti izračunavanje i kasnije dobiti rezultat, može se iskoristiti FutureTask. Primer:
// Kreira bazen niti fiksne veličine
ExecutorService executorService = Executors.newFixedThreadPool(3);
// Kreira niz Callable zadataka
Callable<Integer>[] tasks = new Callable[5];
for (int i = 0; i < tasks.length; i++) {
final int index = i;
tasks[i] = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
TimeUnit.SECONDS.sleep(index + 1);
return (index + 1) * 100;
}
};
}
// Umotava Callable u FutureTask i podnosi u bazen niti
FutureTask<Integer>[] futureTasks = new FutureTask[tasks.length];
for (int i = 0; i < tasks.length; i++) {
futureTasks[i] = new FutureTask<>(tasks[i]);
executorService.submit(futureTasks[i]);
}
// Dobavlja rezultate zadataka
for (int i = 0; i < futureTasks.length; i++) {
System.out.println("Result of task" + (i + 1) + ": " + futureTasks[i].get());
}
// Zatvara bazen niti
executorService.shutdown();Pogledajmo izlaz:
Result of task1: 100
Result of task2: 200
Result of task3: 300
Result of task4: 400
Result of task5: 500Kratak pregled
Ovaj članak detaljno objašnjava kako u Javi iskoristiti Callable, Future i FutureTask za dobavljanje rezultata višenitnog izvršenja.
Na GitHub-u je konačno stigao drugi PDF „Mali priručnik o konkurentnom programiranju“open-source baze znanja sa preko 17000 zvezdica „Ergov put ka naprednom Javom“! Obuhvata osnovne pojmove i načine upotrebe niti, memorijski model Java-e, synchronized, volatile, CAS, AQS, ReentrantLock, bazene niti, konkurentne kontejnere, ThreadLocal, model proizvođač-potrošač i druge teme koje su obavezne za intervjue i razvoj — ukupno preko 150 000 reči i preko 200 ručno nacrtanih ilustracija, što se može opisati kao pristupačno i sa humorom... Više detalja: Odlično, Ergov put ka naprednom konkurentnom programiranju.pdf
