Sự khác biệt giữa các giao diện Runnable và Callable trong Java


Câu trả lời:


444

Xem giải thích tại đây .

Giao diện Callable tương tự Runnable, trong đó cả hai đều được thiết kế cho các lớp có phiên bản có khả năng được thực thi bởi một luồng khác. Tuy nhiên, một Runnable không trả về kết quả và không thể ném ngoại lệ được kiểm tra.


269

Sự khác biệt trong các ứng dụng của RunnableCallable. Là sự khác biệt chỉ với tham số trở lại hiện diện trong Callable?

Về cơ bản, có. Xem câu trả lời cho câu hỏi này . Và javadoc choCallable .

Nhu cầu có cả hai là gì nếu Callablecó thể làm tất cả những gì Runnablekhông?

Bởi vì Runnablegiao diện không thể làm mọi thứ mà Callable!

Runnableđã xuất hiện từ Java 1.0, nhưng Callablechỉ được giới thiệu trong Java 1.5 ... để xử lý các trường hợp sử dụng Runnablekhông hỗ trợ. Về lý thuyết, nhóm Java có thể đã thay đổi chữ ký của Runnable.run()phương thức, nhưng điều này sẽ phá vỡ tính tương thích nhị phân với mã trước 1,5, yêu cầu mã hóa lại khi di chuyển mã Java cũ sang các JVM mới hơn. Đó là một LỚN KHÔNG-KHÔNG. Java cố gắng để tương thích ngược ... và đó là một trong những điểm bán hàng lớn nhất của Java cho máy tính doanh nghiệp.

Và, rõ ràng, có những trường hợp sử dụng trong đó một tác vụ không cần trả về kết quả hoặc ném ngoại lệ được kiểm tra. Đối với những trường hợp sử dụng, việc sử dụng Runnablengắn gọn hơn sử dụng Callable<Void>và trả về nullgiá trị giả ( ) từ call()phương thức.


9
Tôi tự hỏi bạn đã lấy lịch sử này từ đâu. Điều này rất hữu ích.
nhện

4
@prash - những sự thật cơ bản sẽ được tìm thấy trong sách giáo khoa cũ. Giống như phiên bản đầu tiên của Java trong một Nutshell.
Stephen C

4
(@prash - Ngoài ra ... bằng cách bắt đầu sử dụng Java trong kỷ nguyên Java 1.1.)
Stephen C

1
@StephenC Nếu tôi đọc chính xác câu trả lời của bạn, bạn đang đề xuất rằng Runnabletồn tại (phần lớn) vì lý do tương thích ngược. Nhưng không có tình huống nào không cần thiết hoặc quá tốn kém để thực hiện Callablegiao diện ( hoặc yêu cầu) (ví dụ: trong ScheduledFuture<?> ScheduledExecutorService.schedule(Runnable command, long delay, TimeUnit unit))? Vì vậy, không có lợi ích gì trong việc duy trì cả hai giao diện bằng ngôn ngữ ngay cả lịch sử không buộc kết quả hiện tại?
tối đa

1
@max - À tôi nói rồi, và tôi vẫn đồng ý với điều đó. Tuy nhiên, đó là một lý do thứ yếu. Nhưng ngay cả như vậy, tôi nghi ngờ rằng Runnable nó đã được sửa đổi nếu không bắt buộc phải duy trì khả năng tương thích. "Bản tóm tắt" của return null;là một đối số yếu. (Ít nhất, đó sẽ là quyết định của tôi ... trong bối cảnh giả thuyết mà bạn có thể bỏ qua khả năng tương thích ngược.)
Stephen C

82
  • Một Callablenhu cầu để thực hiện call()phương pháp trong khi một Runnablenhu cầu để thực hiện run()phương pháp.
  • A Callablecó thể trả về một giá trị nhưng Runnablekhông thể.
  • A Callablecó thể ném ngoại lệ được kiểm tra nhưng Runnablekhông thể.
  • A Callablecó thể được sử dụng với ExecutorService#invokeXXX(Collection<? extends Callable<T>> tasks)các phương thức nhưng Runnablekhông thể.

    public interface Runnable {
        void run();
    }
    
    public interface Callable<V> {
        V call() throws Exception;
    }

17
ExecutorService.submit (Nhiệm vụ có thể chạy) cũng tồn tại và rất hữu ích
Yair Kukielka

Runnable cũng có thể được sử dụng với ExecutorService theo các cách sau- 1) ExecutorService.execute (Runnable) 2) ExecutorService.submit (Runnable)
Azam Khan

2
Ngoài ra, còn có Executor.submit (tác vụ <T> có thể gọi được) nhưng bạn không thể gọi All hoặc invokeAny với bộ sưu tập các tác vụ Runnable Collection <? mở rộng các tác vụ <T >> có thể gọi được
nikli

36

Tôi tìm thấy điều này trong một blog khác có thể giải thích thêm một chút về những khác biệt này :

Mặc dù cả hai giao diện được triển khai bởi các lớp muốn thực thi trong một luồng thực thi khác nhau, nhưng có một vài khác biệt giữa hai giao diện đó là:

  • Một Callable<V>thể hiện trả về một kết quả của loại V, trong khi một Runnablethể hiện thì không.
  • Một Callable<V>thể hiện có thể đưa ra các ngoại lệ được kiểm tra, trong khi một Runnablethể hiện không thể

Các nhà thiết kế của Java cảm thấy cần phải mở rộng các khả năng của Runnablegiao diện, nhưng họ không muốn ảnh hưởng đến việc sử dụng Runnablegiao diện và có lẽ đó là lý do tại sao họ đi vì có một giao diện riêng có tên Callabletrong Java 1.5 hơn là thay đổi giao diện hiện có Runnable.


27

Chúng ta hãy nhìn vào nơi một người sẽ sử dụng Runnable và Callable.

Cả Runnable và Callable đều chạy trên một luồng khác với luồng gọi. Nhưng Callable có thể trả về một giá trị và Runnable không thể. Vì vậy, nơi này thực sự áp dụng.

Runnable : Nếu bạn có lửa và quên nhiệm vụ thì hãy sử dụng Runnable. Đặt mã của bạn bên trong Runnable và khi phương thức run () được gọi, bạn có thể thực hiện nhiệm vụ của mình. Chuỗi cuộc gọi thực sự không quan tâm khi bạn thực hiện nhiệm vụ của mình.

Có thể gọi được : Nếu bạn đang cố lấy một giá trị từ một tác vụ, thì hãy sử dụng Callable. Bây giờ có thể gọi được trên chính nó sẽ không làm công việc. Bạn sẽ cần một Tương lai mà bạn bao bọc trong Callable của mình và nhận các giá trị của bạn trên tương lai.get (). Tại đây, chuỗi cuộc gọi sẽ bị chặn cho đến khi Tương lai quay lại với kết quả đang chờ phương thức gọi () của Callable thực thi.

Vì vậy, hãy suy nghĩ về một giao diện cho một lớp mục tiêu nơi bạn có cả hai phương thức được gói Runnable và Callable được xác định. Lớp gọi sẽ gọi ngẫu nhiên các phương thức giao diện của bạn mà không biết cái nào là Runnable và cái nào là Callable. Các phương thức Runnable sẽ thực thi không đồng bộ, cho đến khi một phương thức Callable được gọi. Ở đây, luồng của lớp gọi sẽ chặn vì bạn đang truy xuất các giá trị từ lớp đích của mình.

LƯU Ý: Bên trong lớp mục tiêu của bạn, bạn có thể thực hiện các cuộc gọi đến Có thể gọi và Có thể chạy trên một trình thực thi luồng đơn, làm cho cơ chế này tương tự như một hàng đợi gửi nối tiếp. Vì vậy, miễn là người gọi gọi các phương thức được bọc Runnable của bạn, luồng gọi sẽ thực hiện rất nhanh mà không bị chặn. Ngay khi nó gọi một phương thức Callable được gói trong phương thức Tương lai, nó sẽ phải chặn cho đến khi tất cả các mục được xếp hàng khác được thực thi. Chỉ sau đó phương thức sẽ trả về với các giá trị. Đây là một cơ chế đồng bộ hóa.


14

Callablegiao diện khai báo call()phương thức và bạn cần cung cấp tổng quát dưới dạng kiểu cuộc gọi Object () sẽ trả về -

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;
}

Runnablemặt khác là giao diện khai báo run()phương thức được gọi khi bạn tạo một Thread với runnable và gọi start () trên nó. Bạn cũng có thể gọi trực tiếp run () nhưng điều đó chỉ thực thi phương thức run () là cùng một luồng.

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();
}

Để tóm tắt một vài sự khác biệt đáng chú ý là

  1. Một Runnableđối tượng không trả về kết quả trong khi Callableđối tượng trả về kết quả.
  2. Một Runnableđối tượng không thể ném ngoại lệ được kiểm tra, Callableđối tượng có thể ném ngoại lệ.
  3. Các Runnablegiao diện đã được khoảng từ Java 1.0 trong khi Callablechỉ được giới thiệu trong Java 1.5.

Một vài điểm tương đồng bao gồm

  1. Thể hiện của các lớp thực hiện giao diện Runnable hoặc Callable có khả năng được thực thi bởi một luồng khác.
  2. Sơ đồ của cả hai giao diện Callable và Runnable có thể được thực thi bởi ExecutorService thông qua phương thức submit ().
  3. Cả hai đều là các giao diện chức năng và có thể được sử dụng trong các biểu thức Lambda kể từ Java8.

Các phương thức trong giao diện ExecutorService là

<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);

13

Mục đích của các giao diện này từ tài liệu oracle:

Runnable interface nên được thực hiện bởi bất kỳ lớp có trường hợp được dự định sẽ được thực hiện bởi một Thread. Lớp phải định nghĩa một phương thức không có đối số được gọi run.

Có thể gọi được : Một tác vụ trả về kết quả và có thể ném ngoại lệ. Người triển khai định nghĩa một phương thức duy nhất không có đối số được gọi là cuộc gọi. Các Callablegiao diện tương tự như Runnable, trong đó cả hai được thiết kế cho các lớp học có trường hợp được khả năng thực hiện bởi thread khác. A Runnable, tuy nhiên, không trả về kết quả và không thể ném ngoại lệ được kiểm tra.

Sự khác biệt khác:

  1. Bạn có thể vượt qua Runnableđể tạo một Chủ đề . Nhưng bạn không thể tạo Chủ đề mới bằng cách chuyển Callabledưới dạng tham số. Bạn chỉ có thể truyền Callable cho các ExecutorServicephiên bản.

    Thí dụ:

    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();
        }
    
    }
  2. Sử dụng Runnablecho lửa và quên cuộc gọi. Sử dụng Callableđể xác minh kết quả.

  3. Callablecó thể được truyền cho phương thức invoke ALL không giống như Runnable. Phương thức invokeAnyinvokeAllthực hiện các hình thức thực thi hàng loạt hữu ích phổ biến nhất, thực hiện một tập hợp các nhiệm vụ và sau đó chờ ít nhất một hoặc tất cả, để hoàn thành

  4. Sự khác biệt nhỏ: tên phương thức sẽ được thực hiện => run()cho Runnablecall()cho Callable.


11

Vì nó đã được đề cập ở đây Callable là giao diện tương đối mới và nó được giới thiệu như một phần của gói tương tranh. Cả Callable và Runnable đều có thể được sử dụng với người thi hành. Class Thread (thực hiện chính Runnable) chỉ hỗ trợ Runnable.

Bạn vẫn có thể sử dụng Runnable với người thực thi. Ưu điểm của Callable là bạn có thể gửi nó cho người thực thi và ngay lập tức lấy lại kết quả Tương lai sẽ được cập nhật khi quá trình thực thi kết thúc. Điều tương tự có thể được thực hiện với Runnable, nhưng trong trường hợp này bạn phải tự mình quản lý kết quả. Ví dụ: bạn có thể tạo hàng đợi kết quả sẽ giữ tất cả kết quả. Chủ đề khác có thể chờ đợi trên hàng đợi này và đối phó với kết quả đến.


tôi tự hỏi ví dụ về một chủ đề ném ngoại lệ trong java là gì? chủ đề chính sẽ có thể bắt ngoại lệ đó? Nếu không, tôi sẽ không sử dụng Callable. Alex, bạn có cái nhìn sâu sắc về điều này? cảm ơn!
nghìn tỷ

1
Mã chạy trong luồng tùy chỉnh như bất kỳ mã nào khác có thể ném ngoại lệ. Để bắt được nó trong luồng khác, bạn phải thực hiện một số nỗ lực bằng cách sử dụng cơ chế thông báo tùy chỉnh (ví dụ: dựa trên người nghe) hoặc bằng cách sử dụng Futurehoặc thêm hook bắt tất cả các ngoại lệ không suy nghĩ
Hoài

Thông tin tuyệt vời! Cảm ơn, Alex! :)
nghìn tỷ vào

1
Tôi nêu lên câu trả lời này bởi vì nó khẳng định (chính xác nếu được lấy theo mệnh giá) người ta phải sử dụng mô hình nhóm luồng với các đối tượng có thể gọi được. Điều đáng tiếc về điều này là người ta không thể mở rộng Threadđể sử dụng Callablegiao diện một cách có ý nghĩa để một luồng có thể được tùy chỉnh để thực hiện những điều có thể gọi được và những thứ khác mà nhà phát triển có thể muốn. Nếu bất cứ ai đọc bình luận này nghĩ rằng tôi sai, tôi muốn biết rõ hơn ...

8
+-------------------------------------+--------------------------------------------------------------------------------------------------+
|              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                                                                     |
+-------------------------------------+--------------------------------------------------------------------------------------------------+

Các nhà thiết kế của Java cảm thấy cần phải mở rộng các khả năng của Runnablegiao diện, nhưng họ không muốn ảnh hưởng đến việc sử dụng Runnablegiao diện và có lẽ đó là lý do tại sao họ đi vì có một giao diện riêng có tên Callabletrong Java 1.5 hơn là thay đổi giao diện Runnablegiao diện hiện có , là một phần của Java kể từ Java 1.0. nguồn


7

Sự khác biệt giữa Callable và Runnable như sau:

  1. Callable được giới thiệu trong JDK 5.0 nhưng Runnable được giới thiệu trong JDK 1.0
  2. Callable có phương thức call () nhưng Runnable có phương thức run ().
  3. Callable có phương thức gọi trả về giá trị nhưng Runnable có phương thức chạy mà không trả về bất kỳ giá trị nào.
  4. phương thức gọi có thể ném ngoại lệ được kiểm tra nhưng phương thức chạy không thể ném ngoại lệ được kiểm tra.
  5. Có thể sử dụng phương thức submit () để đặt vào hàng đợi tác vụ nhưng phương thức Runnable sử dụng exec () để đặt vào hàng đợi tác vụ.

Điều quan trọng là phải nhấn mạnh rằng Ngoại lệ đã được kiểm tra , không phải là Ngoại lệ
Runtime

5

Cả Callable và Runnable đều tương tự nhau và có thể sử dụng trong việc thực hiện luồng. Trong trường hợp triển khai Runnable, bạn phải triển khai phương thức run () nhưng trong trường hợp có thể gọi được, bạn phải thực hiện phương thức call () , cả hai phương thức đều hoạt động theo cách tương tự nhưng phương thức gọi () có thể gọi linh hoạt hơn. Có một số khác biệt giữa chúng.

Sự khác biệt giữa Runnable và có thể gọi được như dưới đây--

1) Phương thức run () của runnable trả về void , có nghĩa là nếu bạn muốn luồng của bạn trả về một cái gì đó mà bạn có thể sử dụng thêm thì bạn không có lựa chọn nào với phương thức Runnable run () . Có một giải pháp ' Có thể gọi được ' , Nếu bạn muốn trả lại bất kỳ thứ gì ở dạng đối tượng thì bạn nên sử dụng Callable thay vì Runnable . Giao diện có thể gọi có phương thức 'call ()' trả về Object .

Chữ ký phương thức - Runnable->

public void run(){}

Có thể gọi->

public Object call(){}

2) Trong trường hợp phương thức Runnable run () nếu có bất kỳ ngoại lệ được kiểm tra nào phát sinh thì bạn phải xử lý với khối bắt thử , nhưng trong trường hợp phương thức Callable call (), bạn có thể ném ngoại lệ được kiểm tra như bên dưới

 public Object call() throws Exception {}

3) Runnable đến từ phiên bản java 1.0 cũ , nhưng có thể gọi được trong phiên bản Java 1.5 với khung Executer .

Nếu bạn đã quen thuộc với Executers thì bạn nên sử dụng Callable thay vì Runnable .

Mong là bạn hiểu.


2

Runnable (vs) Callable xuất hiện khi chúng ta đang sử dụng khung Executer.

ExecutorService là một giao diện con của Executor, nó chấp nhận cả hai tác vụ Runnable và Callable.

Có thể đạt được Đa luồng trước đó bằng Giao diện Kể từ 1.0 , nhưng ở đây, vấn đề là sau khi hoàn thành tác vụ luồng, chúng tôi không thể thu thập thông tin Chủ đề. Để thu thập dữ liệu, chúng tôi có thể sử dụng các trường Tĩnh.Runnable

Ví dụ Chủ đề riêng biệt để thu thập từng dữ liệu sinh viên.

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() );
}

Để giải quyết vấn đề này, họ đã giới thiệu Kể từ 1.5 , trả về kết quả và có thể đưa ra một ngoại lệ.Callable<V>

  • Phương thức trừu tượng đơn : Cả giao diện Callable và Runnable đều có một phương thức trừu tượng duy nhất, có nghĩa là chúng có thể được sử dụng trong các biểu thức lambda trong java 8.

    public interface Runnable {
    public void run();
    }
    
    public interface Callable<Object> {
        public Object call() throws Exception;
    }

Có một vài cách khác nhau để ủy thác các tác vụ để thực thi cho ExecutorService .

  • execute(Runnable task):void đóng thùng luồng mới nhưng không chặn luồng chính hoặc luồng người gọi vì phương thức này trả về void.
  • submit(Callable<?>):Future<?>, submit(Runnable):Future<?>đóng gói luồng mới và chặn luồng chính khi bạn đang sử dụng tương lai.get () .

Ví dụ về việc sử dụng Giao diện Runnable, Callable với khung Executor.

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();
    }
}

0

Nó là một loại quy ước đặt tên giao diện phù hợp với lập trình chức năng

//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;
}

...
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.