Jaka jest różnica między używaniem interfejsów Runnable
i Callable
podczas projektowania współbieżnego wątku w Javie, dlaczego miałbyś wybierać jeden z nich?
Jaka jest różnica między używaniem interfejsów Runnable
i Callable
podczas projektowania współbieżnego wątku w Javie, dlaczego miałbyś wybierać jeden z nich?
Odpowiedzi:
Zobacz wyjaśnienie tutaj .
Interfejs wywoływalny jest podobny do Runnable, ponieważ oba są przeznaczone dla klas, których instancje są potencjalnie wykonywane przez inny wątek. Runnable jednak nie zwraca wyniku i nie może zgłosić sprawdzonego wyjątku.
Jakie są różnice w aplikacjach
Runnable
iCallable
. Czy różnica jest tylko w przypadku obecności parametru powrotuCallable
?
Zasadniczo tak. Zobacz odpowiedzi na to pytanie . I javadoc dlaCallable
.
Jaka jest potrzeba posiadania obu, jeśli
Callable
może zrobić wszystko, coRunnable
robi?
Ponieważ Runnable
interfejs nie może zrobić wszystkiego, co Callable
robi!
Runnable
istnieje już od wersji Java 1.0, ale Callable
został wprowadzony tylko w wersji Java 1.5 ... do obsługi przypadków użycia, które Runnable
nie obsługują. Teoretycznie zespół Java mógł zmienić sygnaturę Runnable.run()
metody, ale złamałoby to binarną kompatybilność z kodem wcześniejszym niż 1.5, wymagając ponownego przekodowania podczas migracji starego kodu Java do nowszych JVM. To DUŻY NIE-NIE. Java stara się być kompatybilna wstecz ... i to był jeden z największych punktów sprzedaży Java dla komputerów biznesowych.
Oczywiście są przypadki użycia, w których zadanie nie musi zwracać wyniku ani zgłaszać sprawdzonego wyjątku. W przypadku tych przypadków użycie Runnable
jest bardziej zwięzłe niż użycie Callable<Void>
i zwrócenie wartości dummy ( null
) z call()
metody.
Runnable
istnieje (w dużej mierze) ze względu na kompatybilność wsteczną. Ale czy nie są sytuacje, w których wdrożenie (lub wymaganie) Callable
interfejsu (np. W ScheduledFuture<?> ScheduledExecutorService.schedule(Runnable command, long delay, TimeUnit unit)
) jest niepotrzebne lub zbyt drogie ? Czy zatem utrzymanie obu interfejsów w języku nie ma żadnej korzyści, nawet historia nie wymusiła obecnego wyniku?
Runnable
zostałby zmodyfikowany, gdyby nie było konieczności zachowania zgodności. „Płyta kotła” return null;
jest słabym argumentem. (Przynajmniej taka byłaby moja decyzja ... w hipotetycznym kontekście, w którym można zignorować wsteczną kompatybilność.)
Callable
Musi wdrożyć call()
metody natomiast Runnable
musi wdrożyć run()
metody.Callable
może zwrócić wartość, ale Runnable
nie może.Callable
Może rzucić wyjątek sprawdzone ale Runnable
nie mogę.Callable
Może być używany z ExecutorService#invokeXXX(Collection<? extends Callable<T>> tasks)
metod, ale Runnable
nie może być.
public interface Runnable {
void run();
}
public interface Callable<V> {
V call() throws Exception;
}
Znalazłem to na innym blogu, który może wyjaśnić nieco więcej tych różnic :
Chociaż oba interfejsy są implementowane przez klasy, które chcą wykonać w innym wątku wykonania, ale istnieje kilka różnic między tymi dwoma interfejsami, które są:
Callable<V>
Instancji zwraca wynik typu V
, podczas gdy Runnable
instancja nie.Callable<V>
Instancja może rzucać sprawdzonych wyjątków, natomiast Runnable
instancji nie możeProjektanci Java odczuli potrzebę rozszerzenia możliwości Runnable
interfejsu, ale nie chcieli wpływać na wykorzystanie Runnable
interfejsu i prawdopodobnie dlatego zdecydowali się na osobny interfejs o nazwie Callable
Java 1.5, niż zmieniający już istniejących Runnable
.
Przyjrzyjmy się, gdzie można użyć Runnable i Callable.
Zarówno Runnable, jak i Callable działają na innym wątku niż wątek wywołujący. Ale Callable może zwrócić wartość, a Runnable nie. Gdzie to naprawdę ma zastosowanie.
Runnable : Jeśli masz zadanie pożaru i zapomnienia, użyj Runnable. Umieść swój kod w Runnable, a po wywołaniu metody run () możesz wykonać swoje zadanie. Wątek wywołujący naprawdę nie ma znaczenia podczas wykonywania zadania.
Callable : Jeśli próbujesz odzyskać wartość z zadania, użyj Callable. Teraz samodzielne wywołanie nie zadziała. Będziesz potrzebować Przyszłości, którą obejmiesz na żądanie i uzyskasz wartości na future.get (). Tutaj wątek wywołujący zostanie zablokowany, aż Przyszłość powróci z wynikami, które z kolei czekają na wykonanie metody call () Callable.
Pomyśl więc o interfejsie do klasy docelowej, w którym zdefiniowano zarówno metody zawijalne Runnable, jak i Callable. Klasa wywołująca losowo wywołuje metody interfejsu, nie wiedząc, która jest Runnable, a która Callable. Metody Runnable będą wykonywane asynchronicznie, dopóki nie zostanie wywołana metoda Callable. Tutaj wątek klasy wywołującej zostanie zablokowany, ponieważ pobierasz wartości z klasy docelowej.
UWAGA: W klasie docelowej można wykonywać wywołania do wywoływania i uruchamiania na jednym executorze wątków, co czyni ten mechanizm podobny do szeregowej kolejki wysyłania. Tak długo, jak wywołujący wywoła metody opakowane w Runnable, wątek wywołujący będzie działał naprawdę szybko bez blokowania. Gdy tylko wywoła metodę Callable zawiniętą w metodę Future, będzie musiała blokować do momentu wykonania wszystkich pozostałych pozycji w kolejce. Dopiero wtedy metoda zwróci wartości. To jest mechanizm synchronizacji.
Callable
interfejs deklaruje call()
metodę i należy podać ogólne, ponieważ typ wywołania obiektu () powinien zwrócić -
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Runnable
z drugiej strony jest interfejs, który deklaruje run()
metodę, która jest wywoływana podczas tworzenia wątku z runnable i wywołuje na nim start (). Możesz także bezpośrednio wywołać run (), ale to po prostu uruchamia metodę run () w tym samym wątku.
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
Podsumowując kilka znaczących różnic
Runnable
Obiekt nie zwraca wynik podczas gdy Callable
obiekt zwraca wynik.Runnable
Obiekt nie może rzucać sprawdzonej wyjątek ubezpieczeniowy Callable
obiekt może rzucić wyjątek.Runnable
Interfejs istnieje od Java 1.0 natomiast Callable
został wprowadzony dopiero w Javie 1.5.Kilka podobieństw obejmuje
Metody w interfejsie ExecutorService są
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
Cel tych interfejsów z dokumentacji Oracle:
Runnable interfejs powinien być realizowany przez dowolną klasę, której instancje mają być realizowane przez Thread
. Klasa musi zdefiniować metodę bez wywoływanych argumentów run
.
Do wywołania : zadanie, które zwraca wynik i może zgłosić wyjątek. Implementatory definiują pojedynczą metodę bez argumentów nazywaną call. Callable
Interfejs jest podobny do Runnable
, ponieważ oba są przeznaczone dla klas, których przykłady są potencjalnie wykonywane przez inny gwint. A Runnable
nie zwraca jednak wyniku i nie może zgłosić sprawdzonego wyjątku.
Inne różnice:
Możesz przejść, Runnable
aby utworzyć wątek . Ale nie można utworzyć nowego wątku, przekazując go Callable
jako parametr. Można przekazać funkcję wywoływania tylko do ExecutorService
instancji.
public class HelloRunnable implements Runnable {
public void run() {
System.out.println("Hello from a thread!");
}
public static void main(String args[]) {
(new Thread(new HelloRunnable())).start();
}
}
Użyj Runnable
do ognia i zapominaj połączeń. Służy Callable
do weryfikacji wyniku.
Callable
można przekazać do metody invokeAll w przeciwieństwie do metody Runnable
. Metody invokeAny
i invokeAll
wykonywanie najbardziej przydatnych form wykonywania zbiorczego, wykonywanie zbioru zadań, a następnie oczekiwanie na ukończenie co najmniej jednego lub wszystkich
Trywialna różnica: nazwa metody do zaimplementowania => run()
for Runnable
i call()
for Callable
.
Jak już wspomniano tutaj, Callable jest stosunkowo nowym interfejsem i został wprowadzony jako część pakietu współbieżności. Z programami wykonawczymi można używać zarówno wywoływalnych, jak i uruchamialnych. Wątek klasy (który implementuje sam Runnable) obsługuje tylko Runnable.
Nadal możesz używać Runnable z executorami. Zaletą Callable jest to, że możesz wysłać go do executora i natychmiast odzyskać przyszły wynik, który zostanie zaktualizowany po zakończeniu wykonywania. To samo można zaimplementować w Runnable, ale w takim przypadku musisz samodzielnie zarządzać wynikami. Na przykład możesz utworzyć kolejkę wyników, w której będą przechowywane wszystkie wyniki. Inny wątek może poczekać w tej kolejce i poradzić sobie z nadchodzącymi wynikami.
Future
Opartego na słuchaczach) lub używając lub dodając hak, który przechwytuje wszystkie niewykrywane wyjątki: docs.oracle.com/javase/6/docs/api/ java / lang /…
Thread
aby w znaczący sposób korzystać z Callable
interfejsu, tak aby można było dostosować pojedynczy wątek do robienia rzeczy na żądanie i innych rzeczy, których deweloper może chcieć. Jeśli ktoś, kto czyta ten komentarz, uważa, że się mylę, chciałbym wiedzieć lepiej ...
+-------------------------------------+--------------------------------------------------------------------------------------------------+
| Runnable | Callable<T> |
+-------------------------------------+--------------------------------------------------------------------------------------------------+
| Introduced in Java 1.0 of java.lang | Introduced in Java 1.5 of java.util.concurrent library |
| Runnable cannot be parametrized | Callable is a parametrized type whose type parameter indicates the return type of its run method |
| Runnable has run() method | Callable has call() method |
| Runnable.run() returns void | Callable.call() returns a value of Type T |
| Can not throw Checked Exceptions | Can throw Checked Exceptions |
+-------------------------------------+--------------------------------------------------------------------------------------------------+
Projektanci Java odczuli potrzebę rozszerzenia możliwości Runnable
interfejsu, ale nie chcieli wpływać na wykorzystanie Runnable
interfejsu i prawdopodobnie dlatego zdecydowali się na osobny interfejs o nazwie Callable
Java 1.5, niż zmieniający już istniejący Runnable
interfejs, który jest częścią Java od Java 1.0. źródło
Różnice między wywoływalnymi i uruchamialnymi są następujące:
Obie funkcje, które można wywoływać i wywoływać, są do siebie podobne i mogą być używane w implementacji wątku. W przypadku implementacji Runnable musisz zaimplementować metodę run () , ale w przypadku wywołania trzeba zaimplementować metodę call () , obie metody działają w podobny sposób, ale metoda call () ma większą elastyczność. Istnieją między nimi pewne różnice.
Różnica między Runnable i wymagalne jak below--
1) run () metoda przypisanymi zwrotu pustych , środków, jeśli chcesz coś powrotną wątek, który można wykorzystać dodatkowo wtedy masz żadnego wyboru z Runnable run () metody. Istnieje rozwiązanie „Callable”. Jeśli chcesz zwrócić dowolną rzecz w postaci obiektu , powinieneś użyć Callable zamiast Runnable . Interfejs na żądanie ma metodę „call ()”, która zwraca Object .
Podpis metody - Runnable->
public void run(){}
Do wywołania->
public Object call(){}
2) W przypadku Runnable run () metoda sprawdzona wyjątek jeśli powstaje wtedy trzeba trzeba obchodzić się z bloku try catch , ale w przypadku płatnych na żądanie call () metodę, która może rzucić wyjątek sprawdzenia jak poniżej
public Object call() throws Exception {}
3) Runnable pochodzi ze starszej wersji java 1.0 , ale można ją wywołać w wersji Java 1.5 ze środowiskiem Executer .
Jeśli znasz Executorów , powinieneś użyć Callable zamiast Runnable .
Nadzieję, że rozumiesz.
Runnable (vs) Callable zaczyna działać, gdy używamy frameworka Executer.
ExecutorService to podinterface Executor
, który akceptuje zadania Runnable i Callable.
Wcześniej wiele wątków można osiągnąć za pomocą interfejsu od wersji 1.0 , ale tutaj problemem jest to, że po wykonaniu zadania wątku nie jesteśmy w stanie zebrać informacji o wątkach. W celu gromadzenia danych możemy użyć pól statycznych.Runnable
Przykład Oddzielne wątki, aby zebrać dane każdego ucznia.
static HashMap<String, List> multiTasksData = new HashMap();
public static void main(String[] args) {
Thread t1 = new Thread( new RunnableImpl(1), "T1" );
Thread t2 = new Thread( new RunnableImpl(2), "T2" );
Thread t3 = new Thread( new RunnableImpl(3), "T3" );
multiTasksData.put("T1", new ArrayList() ); // later get the value and update it.
multiTasksData.put("T2", new ArrayList() );
multiTasksData.put("T3", new ArrayList() );
}
Aby rozwiązać ten problem, wprowadzili od wersji 1.5, która zwraca wynik i może zgłosić wyjątek.Callable<V>
Pojedyncza metoda abstrakcyjna : Zarówno interfejs wywoływalny, jak i uruchamialny mają jedną metodę abstrakcyjną, co oznacza, że można jej używać w wyrażeniach lambda w Javie 8.
public interface Runnable {
public void run();
}
public interface Callable<Object> {
public Object call() throws Exception;
}
Istnieje kilka różnych sposobów delegowania zadań do wykonania w ExecutorService .
execute(Runnable task):void
tworzy nowy wątek, ale nie blokuje głównego wątku lub wątku wywołującego, ponieważ ta metoda zwraca wartość void.submit(Callable<?>):Future<?>
, submit(Runnable):Future<?>
tworzy nowy wątek i blokuje główny wątek, gdy używasz future.get () .Przykład użycia interfejsów Runnable, Callable with Executor Framework.
class CallableTask implements Callable<Integer> {
private int num = 0;
public CallableTask(int num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " : Started Task...");
for (int i = 0; i < 5; i++) {
System.out.println(i + " : " + threadName + " : " + num);
num = num + i;
MainThread_Wait_TillWorkerThreadsComplete.sleep(1);
}
System.out.println(threadName + " : Completed Task. Final Value : "+ num);
return num;
}
}
class RunnableTask implements Runnable {
private int num = 0;
public RunnableTask(int num) {
this.num = num;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " : Started Task...");
for (int i = 0; i < 5; i++) {
System.out.println(i + " : " + threadName + " : " + num);
num = num + i;
MainThread_Wait_TillWorkerThreadsComplete.sleep(1);
}
System.out.println(threadName + " : Completed Task. Final Value : "+ num);
}
}
public class MainThread_Wait_TillWorkerThreadsComplete {
public static void main(String[] args) throws InterruptedException, ExecutionException {
System.out.println("Main Thread start...");
Instant start = java.time.Instant.now();
runnableThreads();
callableThreads();
Instant end = java.time.Instant.now();
Duration between = java.time.Duration.between(start, end);
System.out.format("Time taken : %02d:%02d.%04d \n", between.toMinutes(), between.getSeconds(), between.toMillis());
System.out.println("Main Thread completed...");
}
public static void runnableThreads() throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(4);
Future<?> f1 = executor.submit( new RunnableTask(5) );
Future<?> f2 = executor.submit( new RunnableTask(2) );
Future<?> f3 = executor.submit( new RunnableTask(1) );
// Waits until pool-thread complete, return null upon successful completion.
System.out.println("F1 : "+ f1.get());
System.out.println("F2 : "+ f2.get());
System.out.println("F3 : "+ f3.get());
executor.shutdown();
}
public static void callableThreads() throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(4);
Future<Integer> f1 = executor.submit( new CallableTask(5) );
Future<Integer> f2 = executor.submit( new CallableTask(2) );
Future<Integer> f3 = executor.submit( new CallableTask(1) );
// Waits until pool-thread complete, returns the result.
System.out.println("F1 : "+ f1.get());
System.out.println("F2 : "+ f2.get());
System.out.println("F3 : "+ f3.get());
executor.shutdown();
}
}
Jest to rodzaj konwencji nazewnictwa interfejsu, która pasuje do programowania funkcjonalnego
//Runnable
interface Runnable {
void run();
}
//Action - throws exception
interface Action {
void run() throws Exception;
}
//Consumer - consumes a value/values, throws exception
interface Consumer1<T> {
void accept(T t) throws Exception;
}
//Callable - return result, throws exception
interface Callable<R> {
R call() throws Exception;
}
//Supplier - returns result, throws exception
interface Supplier<R> {
R get() throws Exception;
}
//Predicate - consumes a value/values, returns true or false, throws exception
interface Predicate1<T> {
boolean test(T t) throws Exception;
}
//Function - consumes a value/values, returns result, throws exception
public interface Function1<T, R> {
R apply(T t) throws Throwable;
}
...