Chọn giữa thực thi của ExecutorService và thực thi của ExecutorService


193

Tôi nên chọn giữa việc gửi hoặc thực thi của ExecutorService như thế nào , nếu giá trị trả về không phải là mối quan tâm của tôi?

Nếu tôi kiểm tra cả hai, tôi đã không thấy bất kỳ sự khác biệt nào giữa hai ngoại trừ giá trị được trả về.

ExecutorService threadExecutor = Executors.newSingleThreadExecutor();
threadExecutor.execute(new Task());

ExecutorService threadExecutor = Executors.newSingleThreadExecutor();
threadExecutor.submit(new Task());

Câu trả lời:


203

Có một sự khác biệt liên quan đến xử lý ngoại lệ / lỗi.

Một nhiệm vụ xếp hàng với execute()mà tạo ra một số Throwablesẽ gây ra UncaughtExceptionHandlercho Threadchạy các nhiệm vụ được gọi. Mặc định UncaughtExceptionHandler, thường in Throwabledấu vết ngăn xếp đến System.err, sẽ được gọi nếu không có trình xử lý tùy chỉnh nào được cài đặt.

Mặt khác, một Throwabletạo ra bởi một nhiệm vụ xếp hàng với submit()ý ràng buộc các Throwablecho Futurerằng đã được sản xuất từ các cuộc gọi đến submit(). Gọi get()vào đó Futuresẽ ném ExecutionExceptionvới bản gốc Throwablelà nguyên nhân của nó (có thể truy cập bằng cách gọi getCause()trên ExecutionException).


19
Lưu ý rằng hành vi này không được đảm bảo vì nó phụ thuộc vào việc bạn Runnablecó được bao bọc trong một Taskhay không, mà bạn có thể không kiểm soát được. Ví dụ, nếu bạn Executorthực sự là một ScheduledExecutorService, nhiệm vụ của bạn sẽ được bao bọc trong một FutureThrowablekhông bị bắt sẽ bị ràng buộc với đối tượng này.
rxg

4
Tôi có nghĩa là "bọc trong một Futurehoặc không", tất nhiên. Ví dụ, hãy xem Javadoc để thực hiện theo lịch trìnhThreadPoolExecutor # .
rxg

59

thực hiện : Sử dụng nó cho lửa và quên cuộc gọi

đệ trình : Sử dụng nó để kiểm tra kết quả của cuộc gọi phương thức và thực hiện hành động thích hợpFutuređối với cuộc gọi bị trả về

Từ javadocs

submit(Callable<T> task)

Gửi một tác vụ trả về giá trị để thực thi và trả về Tương lai biểu thị các kết quả đang chờ xử lý của tác vụ.

Future<?> submit(Runnable task)

Đệ trình một tác vụ Runnable để thực thi và trả về một Tương lai đại diện cho nhiệm vụ đó.

void execute(Runnable command)

Thực hiện lệnh đã cho tại một thời điểm nào đó trong tương lai. Lệnh có thể thực thi trong một luồng mới, trong một luồng được gộp chung hoặc trong luồng gọi, theo quyết định của việc thực thi Executor.

Bạn phải đề phòng trong khi sử dụng submit(). Nó ẩn ngoại lệ trong chính khung trừ khi bạn nhúng mã tác vụ vào try{} catch{}khối.

Mã ví dụ: Mã này nuốt Arithmetic exception : / by zero.

import java.util.concurrent.*;
import java.util.*;

public class ExecuteSubmitDemo{
    public ExecuteSubmitDemo()
    {
        System.out.println("creating service");
        ExecutorService service = Executors.newFixedThreadPool(10);
        //ExtendedExecutor service = new ExtendedExecutor();
        service.submit(new Runnable(){
                 public void run(){
                    int a=4, b = 0;
                    System.out.println("a and b="+a+":"+b);
                    System.out.println("a/b:"+(a/b));
                    System.out.println("Thread Name in Runnable after divide by zero:"+Thread.currentThread().getName());
                 }
            });
        service.shutdown();
    }
    public static void main(String args[]){
        ExecuteSubmitDemo demo = new ExecuteSubmitDemo();
    }
}

đầu ra:

java ExecuteSubmitDemo
creating service
a and b=4:0

Cùng một mã ném bằng cách thay thế submit() bằng execute():

Thay thế

service.submit(new Runnable(){

với

service.execute(new Runnable(){

đầu ra:

java ExecuteSubmitDemo
creating service
a and b=4:0
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
        at ExecuteSubmitDemo$1.run(ExecuteSubmitDemo.java:14)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:744)

Làm thế nào để xử lý các loại kịch bản trong khi sử dụng submit ()?

  1. Nhúng mã tác vụ của bạn (Thực hiện có thể chạy hoặc có thể gọi được) với thử {} bắt {} mã khối
  2. Triển khai thực hiện CustomThreadPoolExecutor

Giải pháp mới:

import java.util.concurrent.*;
import java.util.*;

public class ExecuteSubmitDemo{
    public ExecuteSubmitDemo()
    {
        System.out.println("creating service");
        //ExecutorService service = Executors.newFixedThreadPool(10);
        ExtendedExecutor service = new ExtendedExecutor();
        service.submit(new Runnable(){
                 public void run(){
                    int a=4, b = 0;
                    System.out.println("a and b="+a+":"+b);
                    System.out.println("a/b:"+(a/b));
                    System.out.println("Thread Name in Runnable after divide by zero:"+Thread.currentThread().getName());
                 }
            });
        service.shutdown();
    }
    public static void main(String args[]){
        ExecuteSubmitDemo demo = new ExecuteSubmitDemo();
    }
}

class ExtendedExecutor extends ThreadPoolExecutor {

   public ExtendedExecutor() { 
       super(1,1,60,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(100));
   }
   // ...
   protected void afterExecute(Runnable r, Throwable t) {
     super.afterExecute(r, t);
     if (t == null && r instanceof Future<?>) {
       try {
         Object result = ((Future<?>) r).get();
       } catch (CancellationException ce) {
           t = ce;
       } catch (ExecutionException ee) {
           t = ee.getCause();
       } catch (InterruptedException ie) {
           Thread.currentThread().interrupt(); // ignore/reset
       }
     }
     if (t != null)
       System.out.println(t);
   }
 }

đầu ra:

java ExecuteSubmitDemo
creating service
a and b=4:0
java.lang.ArithmeticException: / by zero

Giải thích rõ nét. Mặc dù mở rộng nó KHÔNG thực sự cần thiết. Chỉ cần đối tượng trong tương lai phải được tiêu thụ để biết liệu nhiệm vụ có thành công hay không. do đó, hãy sử dụng submit () nếu bạn dự định sử dụng Tương lai <t> nếu không, chỉ cần sử dụng
exec

11

nếu bạn không quan tâm đến kiểu trả về, hãy sử dụng thực thi. nó giống như đệ trình, chỉ cần không có sự trở lại của Tương lai.


15
Điều này không đúng theo câu trả lời được chấp nhận. Xử lý ngoại lệ là một sự khác biệt khá đáng kể.
Zero3

7

Lấy từ Javadoc:

Phương thức submitmở rộng phương thức cơ bản {@link Executor # execute} bằng cách tạo và trả về {@link Tương lai} có thể được sử dụng để hủy thực thi và / hoặc chờ hoàn thành.

Cá nhân tôi thích sử dụng thực thi bởi vì nó cảm thấy khai báo nhiều hơn, mặc dù đây thực sự là một vấn đề sở thích cá nhân.

Để cung cấp thêm thông tin: trong trường hợp ExecutorServicethực hiện, việc triển khai cốt lõi được trả về bởi lệnh gọi đến Executors.newSingleThreadedExecutor()ThreadPoolExecutor .

Các submitcuộc gọi được cung cấp bởi cha mẹ của nó AbstractExecutorServicevà tất cả các cuộc gọi thực hiện trong nội bộ. thực hiện được ghi đè / cung cấp bởi ThreadPoolExecutortrực tiếp.


2

Từ Javadoc :

Lệnh có thể thực thi trong một luồng mới, trong một luồng được gộp chung hoặc trong luồng gọi, theo quyết định của việc thực thi Executor.

Vì vậy, tùy thuộc vào việc thực hiện của Executorbạn có thể thấy rằng các khối luồng gửi trong khi tác vụ đang thực thi.


1

Câu trả lời đầy đủ là một thành phần của hai câu trả lời đã được xuất bản ở đây (cộng với một chút "thêm"):

  • Bằng cách gửi một tác vụ (so với thực hiện nó), bạn sẽ lấy lại một tương lai có thể được sử dụng để nhận kết quả hoặc hủy bỏ hành động. Bạn không có loại kiểm soát này khi bạn execute(vì id loại trả về của nó void)
  • executemong đợi một Runnablethời gian submitcó thể lấy một Runnablehoặc một Callableđối số (để biết thêm thông tin về sự khác biệt giữa hai - xem bên dưới).
  • executelàm nổi lên bất kỳ trường hợp ngoại lệ nào không được kiểm tra ngay lập tức (nó không thể ném ngoại lệ được kiểm tra !!!), trong khi submitràng buộc bất kỳ loại ngoại lệ nào trong tương lai sẽ trả về kết quả và chỉ khi bạn gọi future.get()ngoại lệ (được bao bọc) mới được ném. Có thể ném được mà bạn sẽ nhận được là một ví dụ ExecutionExceptionvà nếu bạn gọi đối tượng này, getCause()nó sẽ trả về Bản gốc có thể ném được.

Một vài điểm (liên quan) nữa:

  • Ngay cả khi tác vụ mà bạn muốn submitkhông yêu cầu trả về kết quả, bạn vẫn có thể sử dụng Callable<Void>(thay vì sử dụng a Runnable).
  • Hủy bỏ các nhiệm vụ có thể được thực hiện bằng cách sử dụng cơ chế ngắt . Dưới đây là ví dụ về cách thực hiện chính sách hủy

Tóm lại, đó là một cách thực hành tốt hơn để sử dụng submitvới Callable(so executevới a Runnable). Và tôi sẽ trích dẫn từ "Đồng thời Java trong thực tế" của Brian Goetz:

6.3.2 Nhiệm vụ mang lại kết quả: Có thể gọi và Tương lai

Khung Executor sử dụng Runnable làm đại diện cho nhiệm vụ cơ bản của nó. Runnable là một sự trừu tượng khá hạn chế; chạy không thể trả về giá trị hoặc ném ngoại lệ được kiểm tra, mặc dù nó có thể có tác dụng phụ như ghi vào tệp nhật ký hoặc đặt kết quả trong cấu trúc dữ liệu được chia sẻ. Nhiều tác vụ được tính toán hoãn lại một cách hiệu quả, thực hiện một truy vấn cơ sở dữ liệu, tìm nạp tài nguyên qua mạng hoặc tính toán một hàm phức tạp. Đối với các loại nhiệm vụ này, Callable là một sự trừu tượng hóa tốt hơn: nó hy vọng rằng điểm vào chính, cuộc gọi, sẽ trả về một giá trị và dự đoán rằng nó có thể đưa ra một ngoại lệ.7 Executor bao gồm một số phương thức tiện ích để bọc các loại nhiệm vụ khác, bao gồm cả Runnable và java.security.Priv đặc biệt hành động, với một Callable.


1

Chỉ cần thêm vào câu trả lời được chấp nhận-

Tuy nhiên, các ngoại lệ được ném từ các tác vụ làm cho nó xử lý ngoại lệ chưa được xử lý chỉ cho các tác vụ được gửi với exec (); đối với các tác vụ được gửi cùng với () cho dịch vụ thực thi, mọi ngoại lệ được ném đều được coi là một phần của trạng thái trả về của tác vụ.

Nguồn

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.