إرسال المهام: Runnable مقابل Callable
أرسل مهام Runnable وCallable واسترجع النتائج عبر Future
إرسال المهام: Runnable مقابل Callable درس مجاني في Java Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Java Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Java Academy 4 دروس في المجموع.
Runnable مقابل Callable
تمثل واجهتان وظيفيتان المهام المُرسلة إلى ExecutorService:
- Runnable: لا تعيد قيمة، ولا يمكنها التصريح عن استثناءات مفحوصة
- Callable<V>: تعيد نتيجة من النوع V، ويمكنها طرح استثناءات مفحوصة
import java.util.concurrent.*;
// Runnable — no return value
Runnable r = () -> System.out.println("Running");
// Callable<Integer> — returns a value
Callable<Integer> c = () -> {
return 42; // or do computation
};submit(Runnable)
يعيد submit(Runnable) كائنًا من النوع Future<?>. ويعيد استدعاء get() عليه القيمة null، لكنه يؤكد اكتمال المهمة، أو يطرح استثناءً إذا فشلت.
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<?> future = pool.submit(() ->
System.out.println("Task done on " + Thread.currentThread().getName())
);
future.get(); // waits for completion; returns null
System.out.println("Task confirmed complete");
pool.shutdown();submit(Callable<V>)
يعيد submit(Callable) كائنًا من النوع Future<V>. استدعِ future.get() لاسترداد النتيجة، إذ ينتظر حتى اكتمال المهمة.
ExecutorService pool = Executors.newFixedThreadPool(2);
Callable<Integer> sumTask = () -> {
int sum = 0;
for (int i = 1; i <= 100; i++) sum += i;
return sum;
};
Future<Integer> future = pool.submit(sumTask);
Integer result = future.get(); // blocks until done
System.out.println("Sum 1..100 = " + result); // 5050
pool.shutdown();إرسال Callables متعددة باستخدام invokeAll
يرسل invokeAll() مجموعة من Callables ويعيد List من Futures — وتكون جميعها مكتملة قبل أن يعيد النتيجة:
List<Callable<String>> tasks = List.of(
() -> "Result A",
() -> "Result B",
() -> "Result C"
);
ExecutorService pool = Executors.newFixedThreadPool(3);
List<Future<String>> futures = pool.invokeAll(tasks);
for (Future<String> f : futures) {
System.out.println(f.get());
}
pool.shutdown();invokeAny: أول نتيجة ناجحة
يرسل invokeAny() مجموعة من Callables ويعيد نتيجة أول واحدة تكتمل بنجاح، ويلغي الباقي:
List<Callable<String>> candidates = List.of(
() -> { Thread.sleep(200); return "Slow"; },
() -> { Thread.sleep(50); return "Fast"; },
() -> { Thread.sleep(100); return "Medium"; }
);
ExecutorService pool = Executors.newFixedThreadPool(3);
String winner = pool.invokeAny(candidates);
System.out.println("First: " + winner); // Fast
pool.shutdown();execute() مقابل submit()
يشغّل execute(Runnable) المهمة دون انتظار نتيجتها — فلا يعيد Future، وتُفقد الاستثناءات ما لم تضف معالجًا للاستثناءات غير المعالجة. أما submit() فيغلف المهمة داخل Future، ملتقطًا الاستثناءات لاستردادها عبر get().
ExecutorService pool = Executors.newFixedThreadPool(2);
// execute: exception silently lost
pool.execute(() -> { throw new RuntimeException("Oops!"); });
// submit: exception captured in Future
Future<?> f = pool.submit(() -> { throw new RuntimeException("Oops!"); });
try {
f.get();
} catch (ExecutionException e) {
System.out.println("Caught: " + e.getCause().getMessage());
}
pool.shutdown();Callable مع الاستثناءات المفحوصة
بخلاف Runnable، يمكن لـ Callable طرح استثناءات مفحوصة، مما يجعله مناسبًا طبيعيًا لعمليات الإدخال والإخراج وقواعد البيانات:
Callable<String> dbQuery = () -> {
// This compiles fine — checked exception is declared on Callable.call()
if (Math.random() < 0.5) throw new java.sql.SQLException("DB error");
return "row data";
};
Future<String> f = pool.submit(dbQuery);
try {
String data = f.get();
} catch (ExecutionException e) {
if (e.getCause() instanceof java.sql.SQLException) {
System.out.println("DB failed: " + e.getCause().getMessage());
}
}تنفيذ الحسابات المستقلة بالتوازي
استخدم عمليات إرسال متعددة لـ Callable لتنفيذ الحسابات المستقلة بالتوازي:
ExecutorService pool = Executors.newFixedThreadPool(4);
Future<Long> sumFuture = pool.submit(() -> LongStream.rangeClosed(1,1_000_000).sum());
Future<Long> prodFuture = pool.submit(() -> LongStream.rangeClosed(1,20).reduce(1L, (a,b)->a*b));
long sum = sumFuture.get();
long prod = prodFuture.get();
System.out.println("Sum: " + sum + ", 20!: " + prod);
pool.shutdown();تجميع نتائج Callable
أرسل مهام متعددة واجمع النتائج بعد اكتمالها جميعًا:
int N = 8;
ExecutorService pool = Executors.newFixedThreadPool(N);
List<Future<Integer>> futures = new ArrayList<>();
for (int i = 0; i < N; i++) {
final int chunk = i;
futures.add(pool.submit(() -> chunk * chunk)); // i^2
}
int total = 0;
for (Future<Integer> f : futures) total += f.get();
System.out.println("Sum of squares: " + total);
pool.shutdown();المهلة الزمنية لـ Callable
مرّر مهلة زمنية إلى future.get(timeout, unit) لتجنب الانتظار إلى ما لا نهاية:
Future<String> f = pool.submit(() -> {
Thread.sleep(5000); // slow task
return "done";
});
try {
String result = f.get(1, TimeUnit.SECONDS);
} catch (TimeoutException e) {
System.out.println("Task timed out");
f.cancel(true); // interrupt the task
} catch (ExecutionException e) {
System.out.println("Task failed: " + e.getCause());
}أفضل الممارسات
ملخص أفضل الممارسات:
- استخدم
submit()بدلًا منexecute()— إذ تُلتقط الاستثناءات - استخدم Callable عندما تحتاج إلى قيمة مُعادة أو استثناءات مفحوصة
- استدعِ
future.get()دائمًا مع تحديد مهلة زمنية - استخدم invokeAll لمعالجة الدُفعات، وinvokeAny عندما تكون النتيجة الأولى هي المعتمدة
- أوقِف المجموعة دائمًا
اختبار سريع
ما الميزة الأساسية لـ Callable مقارنةً بـ Runnable؟
مراجعة: Runnable مقابل Callable
أهم النقاط:
- Runnable: يعيد void ولا يدعم الاستثناءات المفحوصة؛ ويعيد submit القيمة Future<?> التي تكون null
- Callable: يعيد V ويمكنه طرح استثناءات مفحوصة؛ ويعيد submit القيمة Future<V>
- invokeAll: يرسل دفعة من المهام وينتظر اكتمالها جميعًا
- invokeAny: يعيد أول نتيجة ناجحة ويلغي الباقي
- استخدم submit() دائمًا لالتقاط الاستثناءات
الأسئلة الشائعة
هل درس «إرسال المهام: Runnable مقابل Callable» مجاني؟
نعم — نص درس «إرسال المهام: Runnable مقابل Callable» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Java Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Java Academy 4 دروس في المجموع.
ماذا ستتعلم في «إرسال المهام: Runnable مقابل Callable»؟
أرسل مهام Runnable وCallable واسترجع النتائج عبر Future تتمرن على Java Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Java Academy؟
لا تُشترط خبرة سابقة. Java Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.
كم من الوقت يستغرق درس «إرسال المهام: Runnable مقابل Callable»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Java Academy هذا؟
نعم. كل درس في Java Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- ExecutorService وأنواع تجمعات الخيوط
- إرسال المهام: Runnable مقابل Callable
- Future ومعالجة الأخطاء
- ScheduledExecutorService للمهام المتكررة