Pilih antara pengiriman ExecutorService dan eksekusi ExecutorService


194

Bagaimana saya harus memilih antara ExecutorService ini mengirimkan atau mengeksekusi , jika nilai yang dikembalikan adalah bukan urusan saya?

Jika saya menguji keduanya, saya tidak melihat perbedaan di antara keduanya kecuali nilai yang dikembalikan.

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

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

Jawaban:


204

Ada perbedaan dalam penanganan pengecualian / kesalahan.

Tugas yang antri dengan execute()yang menghasilkan beberapa Throwableakan menyebabkan UncaughtExceptionHandleruntuk Threadmenjalankan tugas yang akan dipanggil. Default UncaughtExceptionHandler, yang biasanya mencetak Throwablejejak stack System.err, akan dipanggil jika tidak ada handler kustom yang telah diinstal.

Di sisi lain, Throwabledihasilkan oleh tugas yang antri dengan submit()akan mengikat Throwableke Futureyang dihasilkan dari panggilan ke submit(). Memanggil get()yang Futureakan melempar ExecutionExceptiondengan yang asli Throwablesebagai penyebabnya (dapat diakses dengan memanggil getCause()pada ExecutionException).


19
Perhatikan bahwa perilaku ini tidak dijamin karena tergantung pada apakah Anda Runnableakan dibungkus Taskatau tidak, yang mungkin tidak Anda kendalikan. Misalnya, jika Anda Executorbenar-benar seorang ScheduledExecutorService, tugas Anda secara internal akan dibungkus Futuredan tidak Throwableterikat akan terikat pada objek ini.
rxg

4
Maksud saya 'dibungkus dalam Futureatau tidak', tentu saja. Lihat Javadoc untuk mengeksekusi ScheduledThreadPoolExecutor # , misalnya.
rxg

60

mengeksekusi : Gunakan untuk api dan lupakan panggilan

kirim : Gunakan untuk memeriksa hasil pemanggilan metode dan mengambil tindakan yang sesuai padaFuturekeberatan yang dikembalikan oleh pemanggilan

Dari javadocs

submit(Callable<T> task)

Mengirimkan tugas pengembalian nilai untuk dieksekusi dan mengembalikan Masa Depan yang mewakili hasil tugas yang tertunda.

Future<?> submit(Runnable task)

Mengirimkan tugas yang dapat dijalankan untuk dieksekusi dan mengembalikan Masa Depan yang mewakili tugas itu.

void execute(Runnable command)

Menjalankan perintah yang diberikan di beberapa waktu di masa depan. Perintah dapat mengeksekusi di utas baru, dalam utas yang dikumpulkan, atau dalam utas panggilan, atas kebijaksanaan implementasi Pelaksana.

Anda harus berhati-hati saat menggunakan submit(). Itu menyembunyikan pengecualian dalam kerangka itu sendiri kecuali jika Anda menanamkan kode tugas Anda di try{} catch{}blok.

Kode contoh: Kode ini tertelan 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();
    }
}

keluaran:

java ExecuteSubmitDemo
creating service
a and b=4:0

Melemparkan kode yang sama dengan mengganti submit()dengan execute():

Menggantikan

service.submit(new Runnable(){

dengan

service.execute(new Runnable(){

keluaran:

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)

Bagaimana menangani jenis skenario ini saat menggunakan kirim ()?

  1. Sematkan kode Tugas Anda ( Baik Implementasi Runnable atau Callable) dengan coba {} catch {} kode blokir
  2. Melaksanakan CustomThreadPoolExecutor

Solusi baru:

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

keluaran:

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

Penjelasan yang bagus dan bagus. Meskipun memperpanjang itu TIDAK benar-benar diperlukan. Hanya objek masa depan yang harus dikonsumsi untuk mengetahui apakah tugas itu berhasil atau tidak. dengan demikian, gunakan kirim () jika Anda berencana untuk mengkonsumsi Masa Depan <t> jika tidak cukup gunakan eksekusi ()
prash

11

jika Anda tidak peduli dengan tipe pengembalian, gunakan eksekusi. itu sama dengan mengirimkan, hanya tanpa kembalinya Masa Depan.


15
Ini tidak benar sesuai dengan jawaban yang diterima. Penanganan pengecualian adalah perbedaan yang cukup signifikan.
Zero3

7

Diambil dari Javadoc:

Metode submitmemperluas metode dasar {@link Executor # execute} dengan membuat dan mengembalikan {@link Future} yang dapat digunakan untuk membatalkan eksekusi dan / atau menunggu penyelesaian.

Secara pribadi saya lebih suka menggunakan eksekusi karena rasanya lebih deklaratif, meskipun ini benar-benar masalah preferensi pribadi.

Untuk memberikan informasi lebih lanjut: dalam hal ExecutorServiceimplementasi, implementasi inti yang dikembalikan oleh panggilan ke Executors.newSingleThreadedExecutor()adalah a ThreadPoolExecutor.

The submitpanggilan disediakan oleh induknya AbstractExecutorServicedan semua panggilan mengeksekusi secara internal. mengeksekusi ditimpa / disediakan oleh ThreadPoolExecutorlangsung.


2

Dari Javadoc :

Perintah dapat mengeksekusi di utas baru, dalam utas yang dikumpulkan, atau dalam utas panggilan, atas kebijaksanaan implementasi Pelaksana.

Jadi tergantung pada implementasi ExecutorAnda mungkin menemukan bahwa thread mengirimkan blok saat tugas sedang dieksekusi.


1

Jawaban lengkapnya adalah komposisi dari dua jawaban yang dipublikasikan di sini (ditambah sedikit "ekstra"):

  • Dengan mengirimkan tugas (vs. mengeksekusi), Anda mendapatkan masa depan yang dapat digunakan untuk mendapatkan hasil atau membatalkan tindakan. Anda tidak memiliki kontrol semacam ini ketika Anda execute(karena id jenis pengembaliannya void)
  • executemengharapkan Runnablesebentar submitdapat mengambil argumen a Runnableatau a Callable(untuk info lebih lanjut tentang perbedaan antara keduanya - lihat di bawah).
  • executemelembungkan segala pengecualian yang tidak dicentang segera (tidak dapat membuang pengecualian yang diperiksa !!!), sementara submitmengikat segala jenis pengecualian ke masa depan yang kembali sebagai hasilnya, dan hanya ketika Anda memanggil future.get()pengecualian (terbungkus) yang akan dilempar. Throwable yang akan Anda dapatkan adalah instance dari ExecutionExceptiondan jika Anda akan memanggil objek ini, getCause()ia akan mengembalikan Throwable asli.

Beberapa poin (terkait) lainnya:

  • Bahkan jika tugas yang Anda inginkan submittidak memerlukan pengembalian hasil, Anda masih dapat menggunakan Callable<Void>(alih-alih menggunakan aRunnable ).
  • Pembatalan tugas dapat dilakukan dengan menggunakan mekanisme interupsi . Berikut adalah contoh cara menerapkan kebijakan pembatalan

Singkatnya, ini adalah praktik yang lebih baik untuk digunakan submitdengan Callable(vs. executedengan a Runnable). Dan saya akan mengutip dari "Java concurrency in practice" Oleh Brian Goetz:

6.3.2 Tugas yang menghasilkan Hasil: Callable and Future

Kerangka kerja Executor menggunakan Runnable sebagai representasi tugas dasarnya. Runnable adalah abstraksi yang cukup terbatas; run tidak dapat mengembalikan nilai atau melempar pengecualian yang dicentang, meskipun dapat memiliki efek samping seperti menulis ke file log atau menempatkan hasil dalam struktur data bersama. Banyak tugas yang secara efektif menunda perhitungan — mengeksekusi kueri basis data, mengambil sumber daya melalui jaringan, atau menghitung fungsi yang rumit. Untuk jenis-jenis tugas ini, Callable adalah abstraksi yang lebih baik: Callable mengharapkan bahwa titik masuk utama, call, akan mengembalikan nilai dan mengantisipasi bahwa ia mungkin mengeluarkan pengecualian.7 Pelaksana menyertakan beberapa metode utilitas untuk membungkus jenis tugas lain, termasuk Runnable dan java.security.PrivilegedAction, dengan Callable.


1

Hanya menambahkan jawaban yang diterima-

Namun, pengecualian yang dilemparkan dari tugas membuatnya menjadi handler pengecualian tanpa tertangkap hanya untuk tugas yang diajukan dengan eksekusi (); untuk tugas-tugas yang diajukan dengan submit () ke layanan pelaksana, setiap pengecualian yang dilemparkan dianggap sebagai bagian dari status pengembalian tugas.

Sumber

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.