เลือกระหว่างการส่งของ ExecutorService และ ExecutorService


194

ฉันควรเลือกระหว่างการ ส่งหรือดำเนินการของ ExecutorService ได้อย่างไรหากมูลค่าที่ส่งคืนไม่ใช่ข้อกังวลของฉัน

หากฉันทดสอบทั้งสองฉันไม่เห็นความแตกต่างระหว่างสองอย่างนี้ยกเว้นค่าที่ส่งคืน

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

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

คำตอบ:


204

มีข้อแตกต่างเกี่ยวกับการจัดการข้อยกเว้น / ข้อผิดพลาด

งานที่อยู่ในคิวexecute()ที่สร้างบางงานThrowableจะทำให้UncaughtExceptionHandlerการThreadเรียกใช้งานนั้นถูกเรียกใช้ ค่าดีฟอลต์UncaughtExceptionHandlerซึ่งโดยปกติจะพิมพ์การThrowableติดตามสแต็กไปยังSystem.errจะถูกเรียกใช้หากไม่มีการติดตั้งตัวจัดการแบบกำหนดเอง

บนมืออื่น ๆ ที่Throwableสร้างขึ้นโดยงานจัดคิวด้วยsubmit()จะผูกThrowableกับที่ได้รับการผลิตจากการเรียกร้องให้Future submit()การโทรget()ที่Futureจะส่งExecutionExceptionต้นฉบับพร้อมThrowableกับสาเหตุ (สามารถเข้าถึงได้โดยโทรgetCause()ไปที่ExecutionException)


19
โปรดทราบว่าพฤติกรรมนี้ไม่รับประกันเนื่องจากขึ้นอยู่กับว่าคุณRunnableได้รับการห่อหุ้มTaskหรือไม่ซึ่งคุณอาจไม่สามารถควบคุมได้ ตัวอย่างเช่นหากคุณExecutorเป็นคนจริงScheduledExecutorServiceงานของคุณจะได้รับการห่อหุ้มภายในFutureและไม่Throwableถูกจับจะถูกผูกไว้กับวัตถุนี้
rxg

4
ฉันหมายถึง 'พันไว้Futureหรือไม่' แน่นอน ดู Javadoc สำหรับScheduledThreadPoolExecutor # executeตัวอย่างเช่น
rxg

61

ดำเนินการ : ใช้สำหรับการยิงและลืมสาย

ส่ง : ใช้เพื่อตรวจสอบผลลัพธ์ของการเรียกใช้เมธอดและดำเนินการตามความเหมาะสมกับข้อFutureโต้แย้งที่ส่งคืนโดยการโทร

จากjavadocs

submit(Callable<T> task)

ส่งภารกิจการส่งคืนค่าสำหรับการดำเนินการและส่งคืน Future ที่แสดงถึงผลลัพธ์ที่ค้างอยู่ของงาน

Future<?> submit(Runnable task)

ส่งงาน Runnable สำหรับการดำเนินการและส่งคืน Future แทนภารกิจนั้น

void execute(Runnable command)

ดำเนินการคำสั่งที่กำหนดในบางครั้งในอนาคต คำสั่งอาจดำเนินการในเธรดใหม่ในเธรดพูหรือในเธรดการโทรขึ้นอยู่กับดุลยพินิจของการใช้งานผู้บริหาร

submit()คุณต้องใช้ความระมัดระวังในขณะที่ใช้ มันซ่อนข้อยกเว้นในกรอบงานตัวเองเว้นแต่คุณจะฝังรหัสงานของคุณในtry{} catch{}บล็อก

: รหัสตัวอย่าง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();
    }
}

เอาท์พุท:

java ExecuteSubmitDemo
creating service
a and b=4:0

รหัสเดียวกันพ่นโดยแทนที่submit()ด้วยexecute():

แทนที่

service.submit(new Runnable(){

กับ

service.execute(new Runnable(){

เอาท์พุท:

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)

วิธีจัดการกับสถานการณ์ประเภทนี้ในขณะที่ใช้ส่ง ()

  1. ฝังรหัสงานของคุณ ( ไม่ว่าจะเรียกใช้หรือใช้งานได้) โดยลอง {} catch {} รหัสบล็อก
  2. Implement CustomThreadPoolExecutor

โซลูชั่นใหม่:

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

เอาท์พุท:

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

คำอธิบายที่คมชัดดี แม้ว่าการขยายมันไม่จำเป็นจริงๆ เพียงว่าวัตถุในอนาคตจะต้องมีการบริโภคเพื่อที่จะรู้ว่างานที่ประสบความสำเร็จหรือไม่ จึงใช้ส่ง () ถ้าคุณกำลังวางแผนที่จะบริโภคในอนาคต <T> มิฉะนั้นเพียงแค่ใช้รัน ()
prash

11

หากคุณไม่สนใจประเภทผลตอบแทนให้ใช้ execute มันเป็นเช่นเดียวกับที่ส่งโดยไม่มีการกลับมาในอนาคต


15
สิ่งนี้ไม่ถูกต้องตามคำตอบที่ยอมรับ การจัดการข้อยกเว้นเป็นข้อแตกต่างที่สำคัญมาก
Zero3

7

นำมาจาก Javadoc:

วิธีsubmitขยายวิธีการพื้นฐาน {@link Executor # execute} โดยการสร้างและส่งกลับ {@link Future} ที่สามารถใช้เพื่อยกเลิกการดำเนินการและ / หรือรอให้เสร็จสิ้น

โดยส่วนตัวแล้วฉันชอบการใช้งานโปรแกรมเพราะมันให้ความรู้สึกที่ชัดเจนกว่านี้ถึงแม้ว่ามันจะเป็นเรื่องของความชอบส่วนตัวก็ตาม

เพื่อให้ข้อมูลเพิ่มเติม: ในกรณีของExecutorServiceการดำเนินงานการดำเนินงานหลักที่ถูกส่งกลับโดยการเรียกร้องให้เป็นExecutors.newSingleThreadedExecutor()ThreadPoolExecutor

การsubmitโทรถูกจัดเตรียมโดยพาเรนต์AbstractExecutorServiceและการเรียกทั้งหมดดำเนินการภายใน ดำเนินการถูกแทนที่ / จัดทำโดยThreadPoolExecutorโดยตรง


2

จากJavadoc :

คำสั่งอาจดำเนินการในเธรดใหม่ในเธรดพูหรือในเธรดการโทรขึ้นอยู่กับดุลยพินิจของการใช้งานผู้บริหาร

ดังนั้นขึ้นอยู่กับการใช้งานของExecutorคุณอาจพบว่าการส่งบล็อกเธรดในขณะที่งานกำลังดำเนินการ


1

คำตอบแบบเต็มคือองค์ประกอบของสองคำตอบที่เผยแพร่ที่นี่ (บวกเล็กน้อย "พิเศษ"):

  • โดยการส่งงาน (เทียบกับการดำเนินงาน) คุณจะได้รับอนาคตซึ่งสามารถนำมาใช้เพื่อให้ได้ผลลัพธ์หรือยกเลิกการกระทำ คุณไม่มีการควบคุมประเภทนี้เมื่อคุณexecute(เพราะ id ประเภทการส่งคืนvoid)
  • executeคาดว่าRunnableในขณะที่submitสามารถใช้อย่างใดอย่างหนึ่งRunnableหรือCallableเป็นอาร์กิวเมนต์ (สำหรับข้อมูลเพิ่มเติมเกี่ยวกับความแตกต่างระหว่างสอง - ดูด้านล่าง)
  • executeเพิ่มข้อยกเว้นที่ไม่ได้ตรวจสอบทันที (ไม่สามารถโยนข้อยกเว้นที่ตรวจสอบแล้ว !!!) ในขณะที่submitผูกข้อยกเว้นใด ๆในอนาคตที่ส่งกลับผลลัพธ์และเมื่อคุณเรียกfuture.get()ใช้ข้อยกเว้น (ที่ห่อ) จะถูกโยนทิ้ง Throwable ที่คุณจะได้รับเป็นตัวอย่างExecutionExceptionและถ้าคุณเรียกวัตถุนี้getCause()มันจะคืนค่า Throwable ดั้งเดิม

อีกสองสาม (เกี่ยวข้อง) คะแนน:

  • แม้ว่างานที่คุณต้องการsubmitไม่ต้องการผลลัพธ์ แต่คุณยังสามารถใช้งานได้Callable<Void>(แทนที่จะใช้Runnable)
  • การยกเลิกงานสามารถทำได้โดยใช้กลไกการขัดจังหวะ นี่คือตัวอย่างของวิธีการใช้นโยบายการยกเลิก

โดยสรุปแล้วมันเป็นวิธีปฏิบัติที่ดีกว่าในการใช้submitกับCallable(เทียบexecuteกับ a Runnable) และฉันจะอ้างอิงจาก "Java concurrency ในทางปฏิบัติ" โดย Brian Goetz:

6.3.2 งานแบกผลลัพธ์: Callable และอนาคต

กรอบงานผู้บริหารใช้ Runnable เป็นการนำเสนองานขั้นพื้นฐาน Runnable เป็นนามธรรมที่ค่อนข้าง จำกัด การเรียกใช้ไม่สามารถส่งคืนค่าหรือส่งข้อยกเว้นที่ตรวจสอบแม้ว่าจะมีผลข้างเคียงเช่นการเขียนไปยังไฟล์บันทึกหรือการวางผลลัพธ์ในโครงสร้างข้อมูลที่ใช้ร่วมกัน งานจำนวนมากถูกเลื่อนการคำนวณอย่างมีประสิทธิภาพเช่นการดำเนินการค้นหาฐานข้อมูลดึงทรัพยากรผ่านเครือข่ายหรือคำนวณฟังก์ชันที่ซับซ้อน สำหรับประเภทของงานเหล่านี้ Callable เป็นสิ่งที่ดีกว่า: คาดว่าจุดเข้าหลักการโทรจะส่งคืนค่าและคาดหวังว่ามันจะส่งข้อยกเว้น 7 ผู้บริหารมีวิธีการอรรถประโยชน์หลายประการสำหรับการห่องานประเภทอื่นรวมถึง Runnable และ java.security.PrivilegedAction โดยมี Callable


1

เพียงเพิ่มคำตอบที่ยอมรับ -

อย่างไรก็ตามข้อยกเว้นที่ส่งออกจากงานจะส่งไปยังตัวจัดการข้อยกเว้นที่ไม่ได้ตรวจสอบเฉพาะสำหรับงานที่ส่งด้วย execute (); สำหรับงานที่ส่งด้วย submit () ไปยังบริการของผู้ปฏิบัติการข้อยกเว้นที่ส่งออกมาจะถือว่าเป็นส่วนหนึ่งของสถานะการส่งคืนของงาน

แหล่ง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.