อะไรคือความแตกต่างระหว่างการใช้Runnable
และCallable
อินเตอร์เฟสเมื่อออกแบบเธรดที่เกิดขึ้นพร้อมกันใน Java ทำไมคุณถึงเลือกอันใดอันหนึ่ง?
อะไรคือความแตกต่างระหว่างการใช้Runnable
และCallable
อินเตอร์เฟสเมื่อออกแบบเธรดที่เกิดขึ้นพร้อมกันใน Java ทำไมคุณถึงเลือกอันใดอันหนึ่ง?
คำตอบ:
อินเตอร์เฟส Callable นั้นคล้ายกับ Runnable ซึ่งทั้งคู่ได้รับการออกแบบมาสำหรับคลาสที่อินสแตนซ์ถูกเรียกใช้งานโดยเธรดอื่น อย่างไรก็ตาม Runnable ไม่ส่งคืนผลลัพธ์และไม่สามารถโยนข้อยกเว้นที่เลือก
สิ่งที่แตกต่างในการใช้งานของและ
Runnable
Callable
มีความแตกต่างเฉพาะกับพารามิเตอร์การส่งคืนปัจจุบันCallable
หรือไม่
โดยทั่วไปแล้วใช่ ดูคำตอบของคำถามนี้ และJavadoc Callable
สำหรับ
ความจำเป็นของการมีทั้งถ้าเป็นสิ่งที่
Callable
สามารถทำได้ทุกที่Runnable
ที่ไม่?
เนื่องจากRunnable
อินเทอร์เฟซไม่สามารถทำทุกอย่างที่Callable
ทำ!
Runnable
มีมาตั้งแต่ Java 1.0 แต่Callable
ถูกนำมาใช้ใน Java 1.5 ... เพื่อจัดการกรณีใช้งานที่Runnable
ไม่รองรับ ในทางทฤษฎีทีม Java สามารถเปลี่ยนลายเซ็นของRunnable.run()
วิธีการได้ แต่สิ่งนี้จะทำให้ความเข้ากันได้ของไบนารีแบบหักกับโค้ด pre-1.5 ซึ่งต้องการการเข้ารหัสเมื่อทำการย้ายรหัส Java เก่าไปเป็น JVM ที่ใหม่กว่า นั่นคือไม่มีใหญ่ Java มุ่งมั่นที่จะใช้งานร่วมกันได้ย้อนหลัง ... และนั่นเป็นหนึ่งในจุดขายที่ใหญ่ที่สุดของ Java สำหรับการประมวลผลทางธุรกิจ
และเห็นได้ชัดว่ามีกรณีการใช้งานที่งานไม่จำเป็นต้องส่งคืนผลหรือโยนข้อยกเว้นที่ตรวจสอบ สำหรับกรณีการใช้งานเหล่านั้นการใช้Runnable
จะกระชับกว่าการใช้Callable<Void>
และส่งกลับค่าดัมมี่ ( null
) จากcall()
เมธอด
Runnable
มีอยู่ (ส่วนใหญ่) ด้วยเหตุผลด้านความเข้ากันได้แบบย้อนหลัง แต่ไม่มีสถานการณ์ที่ไม่จำเป็นหรือแพงเกินกว่าที่จะใช้งานCallable
อินเทอร์เฟซ ( หรือจำเป็น) (เช่นในScheduledFuture<?> ScheduledExecutorService.schedule(Runnable command, long delay, TimeUnit unit)
) ดังนั้นจะไม่มีประโยชน์ในการรักษาทั้งสองอินเทอร์เฟซในภาษาแม้ประวัติศาสตร์ไม่ได้บังคับผลลัพธ์ปัจจุบัน
Runnable
จะได้รับการแก้ไขหากไม่จำเป็นต้องรักษาความเข้ากันได้ "สำเร็จรูป" ของreturn null;
เป็นอาร์กิวเมนต์ที่อ่อนแอ (อย่างน้อยนั่นก็เป็นการตัดสินใจของฉัน ... ในบริบทสมมุติที่คุณสามารถเพิกเฉยความเข้ากันได้ย้อนหลัง)
Callable
ความต้องการที่จะใช้call()
วิธีการในขณะที่Runnable
ความต้องการที่จะใช้run()
วิธีการCallable
สามารถคืนค่าได้ แต่Runnable
ไม่สามารถCallable
สามารถโยนข้อยกเว้นที่ตรวจสอบแล้ว แต่Runnable
ไม่สามารถCallable
สามารถใช้ร่วมกับExecutorService#invokeXXX(Collection<? extends Callable<T>> tasks)
วิธีการ แต่Runnable
ไม่สามารถ
public interface Runnable {
void run();
}
public interface Callable<V> {
V call() throws Exception;
}
ฉันพบสิ่งนี้ในบล็อกอื่นที่สามารถอธิบายความแตกต่างเหล่านี้ได้มากขึ้น:
แม้ว่าอินเทอร์เฟซทั้งสองจะถูกนำมาใช้โดยคลาสที่ต้องการดำเนินการในเธรดการดำเนินการที่แตกต่างกัน แต่มีความแตกต่างเล็กน้อยระหว่างสองอินเตอร์เฟสซึ่ง ได้แก่ :
Callable<V>
เช่นผลตอบแทนที่เป็นผลมาจากชนิดV
ในขณะที่Runnable
อินสแตนซ์ไม่ได้Callable<V>
เช่นอาจจะโยนข้อยกเว้นการตรวจสอบในขณะที่Runnable
ตัวอย่างเช่นไม่สามารถนักออกแบบของ Java รู้สึกว่าต้องการขยายขีดความสามารถของRunnable
อินเตอร์เฟส แต่พวกเขาไม่ต้องการส่งผลกระทบต่อการใช้งานของRunnable
อินเทอร์เฟซและอาจเป็นเหตุผลว่าทำไมพวกเขาจึงมีส่วนต่อประสานที่แยกชื่อCallable
ใน Java 1.5 แทนที่จะเปลี่ยนไปแล้ว Runnable
ที่มีอยู่
ให้เราดูที่หนึ่งจะใช้ Runnable และ Callable
Runnable และ Callable ทั้งคู่ทำงานในเธรดที่แตกต่างจากเธรดการโทร แต่ Callable สามารถส่งคืนค่าได้และ Runnable ไม่สามารถทำได้ ดังนั้นสิ่งนี้นำไปใช้ที่ไหน
Runnable : ถ้าคุณมีไฟและลืมงานให้ใช้ Runnable วางรหัสของคุณไว้ใน Runnable และเมื่อเรียกใช้เมธอด run () คุณสามารถทำงานของคุณได้ เธรดการโทรไม่สนใจจริงๆเมื่อคุณทำงานของคุณ
Callable : หากคุณพยายามดึงค่าจากงานให้ใช้ Callable ตอนนี้ callable ด้วยตัวเองจะไม่ทำงาน คุณจะต้องมีอนาคตที่ล้อมรอบ Callable ของคุณและรับค่าของคุณใน future.get () ที่นี่เธรดการโทรจะถูกบล็อกจนกว่าอนาคตจะกลับมาพร้อมกับผลลัพธ์ซึ่งกำลังรอวิธีการโทรของ Callable () เพื่อดำเนินการ
ดังนั้นให้คิดถึงอินเทอร์เฟซไปยังคลาสเป้าหมายที่คุณมีทั้งเมธอดที่ล้อมรอบ Runnable และ Callable คลาสการเรียกจะสุ่มเรียกวิธีการอินเทอร์เฟซของคุณโดยไม่รู้ว่า Runnable ใดและ Callable ใด เมธอด Runnable จะดำเนินการแบบอะซิงโครนัสจนกระทั่งมีการเรียกเมธอดที่เรียกได้ นี่คือเธรดของคลาสที่เรียกจะถูกบล็อกเนื่องจากคุณรับค่าจากคลาสเป้าหมายของคุณ
หมายเหตุ: ภายในคลาสเป้าหมายของคุณคุณสามารถโทรไปยัง Callable และ Runnable บนตัวจัดการเธรดเดี่ยวทำให้กลไกนี้คล้ายกับคิวการจัดส่งแบบอนุกรม ดังนั้นตราบใดที่ผู้เรียกเรียกใช้เมธอดที่ห่อของคุณ Runnable เธรดการโทรจะทำงานได้อย่างรวดเร็วโดยไม่ต้องปิดกั้น ทันทีที่มันเรียกวิธีห่อหุ้ม Callable ในอนาคตจะต้องปิดกั้นจนกว่าจะมีการดำเนินการรายการที่อยู่ในคิวอื่น ๆ ทั้งหมด จากนั้นเมธอดจะส่งคืนพร้อมค่า นี่คือกลไกการซิงโครไนซ์
Callable
อินเตอร์เฟซประกาศcall()
วิธีการและคุณต้องให้ข้อมูลทั่วไปตามประเภทของการโทร Object () ควรกลับมา -
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;
}
Runnable
ในทางกลับกันคืออินเทอร์เฟซที่ประกาศrun()
วิธีการที่เรียกว่าเมื่อคุณสร้างเธรดที่มี runnable และ call start () บน นอกจากนี้คุณยังสามารถเรียกใช้ run () ได้โดยตรง แต่นั่นก็เป็นการเรียกใช้เมธอด run () เป็นเธรดเดียวกัน
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();
}
เพื่อสรุปความแตกต่างที่น่าสังเกตคือ
Runnable
วัตถุไม่กลับส่งผลให้ในขณะที่Callable
วัตถุผลตอบแทนRunnable
วัตถุไม่สามารถโยนข้อยกเว้นการตรวจสอบ wheras Callable
วัตถุสามารถโยนข้อยกเว้นRunnable
อินเตอร์เฟซที่ได้รับรอบตั้งแต่ Java 1.0 ในขณะที่Callable
ได้รับการแนะนำเฉพาะใน Java 1.5มีความคล้ายคลึงกันน้อยมาก
วิธีการในส่วนต่อประสาน ExecutorService คือ
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
วัตถุประสงค์ของอินเทอร์เฟซเหล่านี้จากเอกสารของ oracle:
RunnableThread
อินเตอร์เฟซที่ควรจะดำเนินการโดยชั้นใดมีกรณีที่มีความตั้งใจที่จะดำเนินการโดย run
ชั้นจะต้องกำหนดวิธีการของการขัดแย้งใดที่เรียกว่า
Callable : งานที่ส่งคืนผลลัพธ์และอาจส่งข้อยกเว้น ผู้ดำเนินการกำหนดวิธีการเดียวโดยไม่มีข้อโต้แย้งที่เรียกว่าโทร Callable
อินเตอร์เฟซที่มีความคล้ายคลึงกับRunnable
ในการที่ทั้งสองได้รับการออกแบบสำหรับการเรียนที่มีกรณีที่อาจจะมีการดำเนินการโดยหัวข้ออื่น Runnable
แต่ไม่กลับผลและไม่สามารถโยนข้อยกเว้นการตรวจสอบ
ความแตกต่างอื่น ๆ :
คุณสามารถส่งผ่านRunnable
เพื่อสร้างกระทู้ แต่คุณไม่สามารถสร้างเธรดใหม่ด้วยการส่งผ่านCallable
พารามิเตอร์ คุณสามารถส่งผ่าน Callable ไปยังExecutorService
อินสแตนซ์เท่านั้น
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();
}
}
ใช้Runnable
สำหรับยิงและลืมสาย ใช้Callable
เพื่อตรวจสอบผลลัพธ์
Callable
สามารถส่งผ่านไปยังinvokeวิธีที่แตกต่างRunnable
กันทั้งหมด วิธีการinvokeAny
และinvokeAll
ดำเนินการในรูปแบบที่มีประโยชน์มากที่สุดของการดำเนินการแบบกลุ่มเรียกใช้งานคอลเลกชันจากนั้นรออย่างน้อยหนึ่งหรือทั้งหมดเพื่อให้เสร็จสมบูรณ์
ความแตกต่างเล็กน้อย: ชื่อวิธีที่จะดำเนินการ => run()
สำหรับRunnable
และสำหรับcall()
Callable
ตามที่ได้กล่าวมาแล้วที่นี่ Callable เป็นอินเทอร์เฟซที่ค่อนข้างใหม่และถูกนำมาใช้เป็นส่วนหนึ่งของแพ็คเกจการทำงานพร้อมกัน ทั้ง Callable และ Runnable สามารถใช้กับ executors ได้ Class Thread (ที่ใช้ Runnable เอง) รองรับ Runnable เท่านั้น
คุณยังสามารถใช้ Runnable กับผู้ปฏิบัติการได้ ข้อดีของ Callable ที่คุณสามารถส่งไปยังผู้ปฏิบัติการและรับผลในอนาคตที่จะได้รับการปรับปรุงทันทีเมื่อการดำเนินการเสร็จสิ้น สิ่งเดียวกันอาจถูกนำไปใช้กับ Runnable แต่ในกรณีนี้คุณต้องจัดการผลลัพธ์ด้วยตัวเอง ตัวอย่างเช่นคุณสามารถสร้างคิวผลลัพธ์ที่จะเก็บผลลัพธ์ทั้งหมด เธรดอื่นสามารถรอคิวนี้และจัดการกับผลลัพธ์ที่มาถึง
Future
หรือโดยการเพิ่มเบ็ดที่จับข้อยกเว้นที่ไม่ต้องการทั้งหมด: docs.oracle.com/javase/6/docs/api/ java / lang / …
Thread
การใช้Callable
อินเทอร์เฟซที่มีความหมายเพื่อให้สามารถปรับแต่งเธรดเดี่ยวเพื่อทำสิ่งที่เรียกได้และสิ่งอื่น ๆ ที่นักพัฒนาอาจต้องการ หากใครที่อ่านความคิดเห็นนี้คิดว่าฉันผิดฉันอยากรู้ดีกว่า ...
+-------------------------------------+--------------------------------------------------------------------------------------------------+
| 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 |
+-------------------------------------+--------------------------------------------------------------------------------------------------+
นักออกแบบของ Java รู้สึกว่าต้องการขยายขีดความสามารถของRunnable
อินเตอร์เฟส แต่พวกเขาไม่ต้องการส่งผลกระทบต่อการใช้งานของRunnable
อินเทอร์เฟซและอาจเป็นเหตุผลว่าทำไมพวกเขาจึงมีส่วนต่อประสานที่แยกชื่อCallable
ใน Java 1.5 แทนที่จะเปลี่ยนไปแล้วRunnable
อินเตอร์เฟสที่มีอยู่ซึ่งเป็นส่วนหนึ่งของ Java ตั้งแต่ Java 1.0 แหล่ง
ความแตกต่างระหว่าง Callable และ Runnable มีดังต่อไปนี้:
Callable และRunnableทั้งสองมีความคล้ายคลึงกันและสามารถใช้ในการใช้ด้าย ในกรณีที่ใช้งานRunnableคุณต้องใช้วิธีrun ()แต่ในกรณีที่ callable คุณจะต้องใช้วิธีการcall ()ทั้งสองวิธีจะทำงานในลักษณะที่คล้ายกัน แต่วิธี callable ()มีความยืดหยุ่นมากขึ้น
ความแตกต่างระหว่างRunnableและcallableดังนี้ -
1) เมธอดrun ()ของrunnableส่งคืนโมฆะหมายความว่าถ้าคุณต้องการให้เธรดของคุณส่งคืนบางสิ่งที่คุณสามารถใช้เพิ่มเติมได้จากนั้นคุณไม่มีทางเลือกด้วยเมธอดRunnable run () มีการแก้ปัญหาคือ'Callable'ถ้าคุณต้องการที่จะกลับสิ่งใด ๆ ในรูปแบบของวัตถุแล้วคุณควรใช้ Callable แทน Runnable อินเตอร์เฟซ Callable มีวิธีการ'โทร ()' ซึ่งผลตอบแทนวัตถุ
ลายเซ็นเมธอด - Runnable->
public void run(){}
Callable->
public Object call(){}
2) ในกรณีที่Runnable run ()วิธีการถ้ามีการตรวจสอบข้อยกเว้นเกิดขึ้นคุณจะต้องจัดการกับลอง catch catch blockแต่ในกรณีที่Callable call ()วิธีการคุณสามารถโยนยกเว้นการตรวจสอบดังต่อไปนี้
public Object call() throws Exception {}
3) Runnableมาจากรุ่นjava 1.0รุ่นเก่า แต่callableมาในรุ่นJava 1.5 ที่มีเฟรมเวิร์กExecuter
หากคุณมีความคุ้นเคยกับExecutersแล้วคุณควรใช้ Callable แทน Runnable
หวังว่าคุณจะเข้าใจ.
Runnable (vs) Callableเข้ามาในจุดเมื่อเราใช้ Executer framework
ExecutorService เป็นส่วนย่อยของExecutor
ซึ่งยอมรับทั้งงาน Runnable และ Callable
Multi-Threading ก่อนหน้านี้สามารถทำได้โดยใช้ Interface ตั้งแต่ 1.0แต่ที่นี่ปัญหาคือหลังจากทำงานเธรดเสร็จแล้วเราไม่สามารถรวบรวมข้อมูลเธรดได้ เพื่อรวบรวมข้อมูลเราอาจใช้ฟิลด์คงที่Runnable
ตัวอย่างคั่นหัวข้อเพื่อรวบรวมข้อมูลนักเรียนแต่ละคน
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() );
}
เพื่อแก้ไขปัญหานี้พวกเขาได้แนะนำตั้งแต่ 1.5ซึ่งส่งคืนผลลัพธ์และอาจมีข้อยกเว้นCallable<V>
Single Abstract Method : ทั้ง Callable และ Runnable interface มีวิธีนามธรรมเดียวซึ่งหมายความว่าสามารถใช้ในการแสดงออกแลมบ์ดาใน java 8
public interface Runnable {
public void run();
}
public interface Callable<Object> {
public Object call() throws Exception;
}
มีวิธีการที่แตกต่างกันไม่กี่ที่จะมอบหมายงานสำหรับการดำเนินการกับผู้มีExecutorService
execute(Runnable task):void
สร้างเธรดใหม่ แต่ไม่บล็อกเธรดหลักหรือเธรดผู้เรียกเนื่องจากเมธอดนี้ส่งคืนโมฆะsubmit(Callable<?>):Future<?>
, submit(Runnable):Future<?>
ลังหัวข้อใหม่และบล็อกหัวข้อหลักเมื่อคุณกำลังใช้future.get ()ตัวอย่างของการใช้อินเทอร์เฟซเรียกใช้ได้เรียกใช้ได้ด้วยกรอบงานของผู้บริหาร
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();
}
}
มันเป็นประเภทของการตั้งชื่ออินเตอร์เฟซที่ตรงกับการเขียนโปรแกรมการทำงาน
//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;
}
...