Runnable vs. Callable di Java

1. Gambaran keseluruhan

Sejak awal Java, multithreading telah menjadi aspek utama bahasa. Runnable adalah antara muka teras yang disediakan untuk mewakili tugas multi-threaded dan Callable adalah versi Runnable yang ditingkatkan yang ditambahkan di Java 1.5.

Dalam artikel ini, kita akan meneroka perbezaan dan aplikasi kedua-dua antara muka.

2. Mekanisme Pelaksanaan

Kedua-dua antara muka ini dirancang untuk mewakili tugas yang dapat dilaksanakan oleh beberapa utas. Runnable tugas boleh dijalankan menggunakan Thread kelas atau ExecutorService manakala Callables boleh dijalankan hanya menggunakan kedua.

3. Nilai Pulangan

Mari kita lihat dengan lebih mendalam cara antara muka ini menangani nilai kembali.

3.1. Dengan Runnable

Antara muka Runnable adalah antara muka fungsional dan mempunyai kaedah single run () yang tidak menerima parameter apa pun dan tidak mengembalikan nilai.

Ini sesuai untuk situasi di mana kita tidak mencari hasil pelaksanaan utas, misalnya, pembalakan peristiwa masuk:

public interface Runnable { public void run(); }

Mari kita fahami ini dengan contoh:

public class EventLoggingTask implements Runnable{ private Logger logger = LoggerFactory.getLogger(EventLoggingTask.class); @Override public void run() { logger.info("Message"); } }

Dalam contoh ini, utas hanya akan membaca mesej dari barisan dan memasukkannya ke dalam fail log. Tidak ada nilai yang dikembalikan dari tugas; tugas tersebut dapat dilancarkan menggunakan ExecutorService:

public void executeTask() { executorService = Executors.newSingleThreadExecutor(); Future future = executorService.submit(new EventLoggingTask()); executorService.shutdown(); }

Dalam kes ini, objek Masa Depan tidak akan mempunyai nilai.

3.2. Dengan Boleh Dipanggil

Antara muka Callable adalah antara muka generik yang mengandungi kaedah panggilan tunggal () - yang mengembalikan nilai generik V :

public interface Callable { V call() throws Exception; }

Mari kita lihat pengiraan faktorial nombor:

public class FactorialTask implements Callable { int number; // standard constructors public Integer call() throws InvalidParamaterException { int fact = 1; // ... for(int count = number; count > 1; count--) { fact = fact * count; } return fact; } }

Hasil kaedah panggilan () dikembalikan dalam objek Masa Depan :

@Test public void whenTaskSubmitted_ThenFutureResultObtained(){ FactorialTask task = new FactorialTask(5); Future future = executorService.submit(task); assertEquals(120, future.get().intValue()); }

4. Pengendalian Pengecualian

Mari lihat betapa sesuai untuk pengendalian pengecualian.

4.1. Dengan Runnable

Oleh kerana tandatangan kaedah tidak mempunyai klausa "lemparan" yang ditentukan,tidak ada cara untuk menyebarkan pengecualian yang diperiksa lebih lanjut.

4.2. Dengan Boleh Dipanggil

Kaedah Callable's call () mengandungi klausa "lempar Pengecualian" sehingga kami dapat menyebarkan pengecualian yang diperiksa lebih jauh:

public class FactorialTask implements Callable { // ... public Integer call() throws InvalidParamaterException { if(number < 0) { throw new InvalidParamaterException("Number should be positive"); } // ... } }

Dalam hal menjalankan Dipanggil menggunakan satu ExecutorService, pengecualian dikumpulkan dalam Future objek, yang boleh disemak dengan membuat panggilan kepada Future.get () kaedah. Ini akan menimbulkan ExecutionException - yang membungkus pengecualian asal:

@Test(expected = ExecutionException.class) public void whenException_ThenCallableThrowsIt() { FactorialCallableTask task = new FactorialCallableTask(-5); Future future = executorService.submit(task); Integer result = future.get().intValue(); }

Dalam ujian di atas, ExecutionException dilemparkan kerana kami melewati nombor yang tidak sah. Kita boleh memanggil kaedah getCause () pada objek pengecualian ini untuk mendapatkan pengecualian yang diperiksa semula.

Jika kita tidak membuat panggilan kepada kenalan () kaedah Future kelas - kemudian pengecualian yang dilemparkan oleh panggilan () kaedah tidak akan dilaporkan kembali, dan tugas itu masih akan ditandakan sebagai selesai:

@Test public void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){ FactorialCallableTask task = new FactorialCallableTask(-5); Future future = executorService.submit(task); assertEquals(false, future.isDone()); }

Ujian di atas akan berjaya walaupun kita telah memberikan pengecualian untuk nilai negatif parameter ke FactorialCallableTask.

5. Kesimpulan

Dalam artikel ini, kami telah meneroka perbezaan antara antara muka Runnable dan Callable .

Seperti biasa, kod lengkap untuk artikel ini terdapat di GitHub.