سؤال

ما هو الفرق بين استخدام Runnable و Callable واجهات عند تصميم سلسلة رسائل متزامنة في Java، لماذا تختار واحدة على الأخرى؟

هل كانت مفيدة؟

المحلول

انظر الشرح هنا.

تشبه الواجهة القابلة للتطبيق Runnable ، حيث تم تصميم كلاهما للفئات التي يمكن تنفيذ مثيلاتها بواسطة موضوع آخر. ومع ذلك ، لا يعيد Runnable نتيجة ولا يمكنه إلقاء استثناء محدد.

نصائح أخرى

ما هي الاختلافات في التطبيقات Runnable و Callable.هل الفرق فقط مع معلمة الإرجاع الموجودة في Callable?

في الأساس، نعم.شاهد الإجابات على هذا السؤال.و ال جافادوك ل Callable.

ما هي الحاجة إلى وجود كليهما إذا Callable يمكن أن تفعل كل ذلك Runnable يفعل؟

بسبب ال Runnable واجهه المستخدم لا تستطيع افعل كل ذلك Callable يفعل!

Runnable كان موجودًا منذ Java 1.0، ولكن Callable تم تقديمه فقط في Java 1.5 ...للتعامل مع حالات الاستخدام التي Runnable لا يدعم.من الناحية النظرية، كان بإمكان فريق Java تغيير توقيع ملف Runnable.run() الطريقة، ولكن هذا من شأنه أن يؤدي إلى كسر التوافق الثنائي مع كود ما قبل 1.5، مما يتطلب إعادة الترميز عند ترحيل كود Java القديم إلى JVMs الأحدث.هذا هو لا لا كبيرة.تسعى Java جاهدة لتكون متوافقة مع الإصدارات السابقة ...وكانت هذه واحدة من أكبر نقاط البيع في Java لحوسبة الأعمال.

ومن الواضح أن هناك حالات استخدام لا تنطبق عليها المهمة يحتاج لإرجاع نتيجة أو طرح استثناء محدد.بالنسبة لحالات الاستخدام هذه، استخدم Runnable هو أكثر إيجازا من الاستخدام Callable<Void> وإرجاع دمية (null) القيمة من call() طريقة.

  • أ 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 المثال لا يمكن

شعر مصممو جافا بالحاجة إلى توسيع قدرات Runnable الواجهة، لكنهم لا يريدون التأثير على استخدامات Runnable الواجهة وربما كان هذا هو السبب وراء اختيارهم لواجهة منفصلة مسماة Callable في Java 1.5 بدلاً من تغيير الموجود بالفعل Runnable.

دعونا نلقي نظرة على المكان الذي يمكن أن يستخدم فيه Runnable و Callable.

يعمل كل من Runnable و Callable على مؤشر ترابط مختلف عن مؤشر الترابط المتصل.لكن يمكن لـ Callable إرجاع قيمة ولا يمكن لـ Runnable ذلك.فأين ينطبق هذا حقا.

قابل للتشغيل :إذا كانت لديك مهمة "أشعلت ونسيت" فاستخدم Runnable.ضع الكود الخاص بك داخل Runnable وعندما يتم استدعاء الأسلوب run()، يمكنك تنفيذ مهمتك.لا يهتم مؤشر ترابط الاتصال حقًا عند تنفيذ مهمتك.

قابل للاستدعاء :إذا كنت تحاول استرداد قيمة من مهمة، فاستخدم Callable.الآن يمكن الاستدعاء من تلقاء نفسه لن يقوم بهذه المهمة.ستحتاج إلى مستقبل تلتف حوله قابل للاستدعاء وتحصل على القيم الخاصة بك على Future.get ().هنا سيتم حظر مؤشر ترابط الاتصال حتى يعود المستقبل بالنتائج التي بدورها تنتظر تنفيذ طريقة call() الخاصة بـ Callable.

لذا فكر في واجهة لفئة مستهدفة حيث تم تحديد الأساليب الملفوفة القابلة للتشغيل والقابلة للاستدعاء.سوف تقوم فئة الاستدعاء بشكل عشوائي باستدعاء طرق الواجهة الخاصة بك دون معرفة ما هو قابل للتشغيل وما هو قابل للاستدعاء.سيتم تنفيذ الأساليب القابلة للتشغيل بشكل غير متزامن، حتى يتم استدعاء طريقة قابلة للاستدعاء.هنا سيتم حظر مؤشر ترابط فئة الاتصال نظرًا لأنك تقوم باسترداد القيم من فئتك المستهدفة.

ملحوظة :داخل فئتك المستهدفة، يمكنك إجراء استدعاءات لـ Callable و Runnable على منفذ تنفيذي واحد، مما يجعل هذه الآلية مشابهة لقائمة انتظار الإرسال التسلسلية.طالما أن المتصل يستدعي أساليب Runnable الملفوفة الخاصة بك، فسيتم تنفيذ مؤشر ترابط الاتصال بسرعة كبيرة دون حظر.بمجرد استدعاء طريقة قابلة للاستدعاء ملفوفة في المستقبل، سيتعين عليها حظرها حتى يتم تنفيذ كافة العناصر الأخرى في قائمة الانتظار.عندها فقط ستعود الطريقة بالقيم.هذه هي آلية المزامنة.

Callable تعلن الواجهة call() الطريقة وتحتاج إلى توفير الأدوية العامة كنوع من كائن استدعاء () يجب أن يعود -

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() الطريقة التي يتم استدعاؤها عند إنشاء مؤشر ترابط باستخدام العنصر القابل للتشغيل واستدعاء 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();
}

لتلخيص بعض الاختلافات الملحوظة هي

  1. أ Runnable الكائن لا يُرجع نتيجة بينما a Callable يقوم الكائن بإرجاع النتيجة.
  2. أ Runnable لا يمكن للكائن طرح استثناء محدد بينما a Callable يمكن للكائن رمي استثناء.
  3. ال Runnable كانت الواجهة موجودة منذ Java 1.0 بينما Callable تم تقديمه فقط في Java 1.5.

تشمل أوجه التشابه القليلة

  1. من المحتمل أن يتم تنفيذ مثيلات الفئات التي تنفذ واجهات قابلة للتشغيل أو القابلة للاتصال بواسطة مؤشر ترابط آخر.
  2. يمكن تنفيذ مثيل الواجهات القابلة للاستدعاء والقابلة للتشغيل بواسطة ExecutorService عبر طريقة الإرسال ().
  3. كلاهما واجهات وظيفية ويمكن استخدامها في تعبيرات Lambda منذ Java8.

الطرق الموجودة في واجهة ExecutorService هي

<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);

الغرض من هذه الواجهات من وثائق أوراكل:

قابل للتشغيل يجب أن يتم تنفيذ الواجهة بواسطة أي فئة تم تصميم مثيلاتها للتنفيذ بواسطة a Thread.يجب أن يحدد الفصل طريقة لا يتم استدعاء أي وسيطات فيها run.

قابل للاستدعاء:مهمة تقوم بإرجاع نتيجة وقد تطرح استثناءً.يحدد المنفذون طريقة واحدة بدون وسائط تسمى call.ال Callable واجهة مشابهة ل Runnable, ، حيث أن كلاهما مصمم للفئات التي من المحتمل أن يتم تنفيذ مثيلاتها بواسطة مؤشر ترابط آخر.أ Runnable, ومع ذلك، لا يُرجع نتيجة ولا يمكنه طرح استثناء محدد.

اختلافات أخرى:

  1. يمكنك تمرير 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();
        }
    
    }
    
  2. يستخدم Runnable لاطلاق النار وننسى المكالمات.يستخدم Callable للتحقق من النتيجة.

  3. Callable يمكن أن تنتقل إلى استدعاء الكل طريقة خلافا Runnable.طُرق invokeAny و invokeAll تنفيذ الأشكال الأكثر شيوعًا للتنفيذ المجمع، وتنفيذ مجموعة من المهام ثم انتظار واحدة على الأقل أو جميعها لإكمالها

  4. فرق تافه :اسم الطريقة المراد تنفيذها => run() ل Runnable و call() ل Callable.

كما ذكرنا سابقًا، تعد Callable واجهة جديدة نسبيًا وقد تم تقديمها كجزء من حزمة التزامن.يمكن استخدام كل من Callable و Runnable مع المنفذين.يدعم Class Thread (الذي ينفذ Runnable نفسه) Runnable فقط.

لا يزال بإمكانك استخدام Runnable مع المنفذين.ميزة Callable أنه يمكنك إرسالها إلى المنفذ واستعادة النتيجة المستقبلية على الفور والتي سيتم تحديثها عند الانتهاء من التنفيذ.يمكن تنفيذ الشيء نفسه مع Runnable، ولكن في هذه الحالة عليك إدارة النتائج بنفسك.على سبيل المثال، يمكنك إنشاء قائمة انتظار النتائج التي ستحتوي على كافة النتائج.يمكن للخيط الآخر الانتظار في قائمة الانتظار هذه والتعامل مع النتائج التي تصل.

+-------------------------------------+--------------------------------------------------------------------------------------------------+
|              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                                                                     |
+-------------------------------------+--------------------------------------------------------------------------------------------------+

شعر مصممو جافا بالحاجة إلى توسيع قدرات Runnable الواجهة، لكنهم لا يريدون التأثير على استخدامات Runnable الواجهة وربما كان هذا هو السبب وراء اختيارهم لواجهة منفصلة مسماة Callable في Java 1.5 بدلاً من تغيير الموجود بالفعل Runnable الواجهة التي كانت جزءًا من Java منذ Java 1.0. مصدر

الفرق بين Callable و Runnable كما يلي:

  1. تم تقديم Callable في JDK 5.0 ولكن تم تقديم Runnable في JDK 1.0
  2. لدى Callable طريقة call() لكن Runnable لديه طريقة run().
  3. لدى Callable طريقة استدعاء تُرجع القيمة ولكن Runnable لديه طريقة تشغيل لا تُرجع أي قيمة.
  4. يمكن لأسلوب الاستدعاء طرح الاستثناء المحدد ولكن لا يمكن لأسلوب التشغيل طرح الاستثناء المحدد.
  5. استخدم طريقة الإرسال () القابلة للاستدعاء لوضعها في قائمة انتظار المهام ولكن استخدم طريقة Runnable طريقة التنفيذ () لوضعها في قائمة انتظار المهام.

قابلة للاستدعاء و قابل للتشغيل كلاهما مشابه لبعضهما البعض ويمكن استخدامه في تنفيذ الخيط.في حالة التنفيذ قابل للتشغيل يجب عليك التنفيذ يجري() الطريقة ولكن في حالة الاستدعاء يجب أن تحتاج إلى التنفيذ يتصل() الطريقة، كلا الطريقتين تعملان بطرق مماثلة ولكن قابلة للاستدعاء يتصل() تتمتع الطريقة بمرونة أكبر. هناك بعض الاختلافات بينهما.

الفرق بين قابل للتشغيل و قابل للاستدعاء على النحو التالي--

1) ال يجري() طريقة قابل للتشغيل عائدات فارغ, ، يعني أنه إذا كنت تريد أن يقوم مؤشر الترابط الخاص بك بإرجاع شيء يمكنك استخدامه بشكل أكبر، فلديك لا يوجد خيار مع Runnable run() طريقة.هل هناك حل "قابل للاستدعاء", ، إذا كنت تريد إرجاع أي شيء على شكل هدف ثُم أنت يجب استخدام Callable بدلاً من Runnable.واجهة قابلة للاستدعاء لها طريقة "call()" الذي يُرجع الكائن.

توقيع الطريقة - Runnable->

public void run(){}

قابل للاستدعاء->

public Object call(){}

2) في حالة تشغيل قابل للتشغيل () الطريقة في حالة ظهور أي استثناء محدد، فيجب عليك ذلك تحتاج إلى التعامل معها باستخدام كتلة محاولة الصيد, ، ولكن في حالة مكالمة قابلة للاستدعاء () طريقة لك يمكن رمي الاستثناء المحدد على النحو التالي

 public Object call() throws Exception {}

3) قابل للتشغيل يأتي من التراث جافا 1.0 النسخة ولكن قابل للاستدعاء دخل جافا 1.5 النسخة مع المنفذ نطاق.

إذا كنت على دراية المنفذين ثم يجب عليك استخدم Callable بدلاً من Runnable.

أتمنى أن تتفهم.

قابل للتشغيل (مقابل) قابل للاستدعاء يأتي حيز التنفيذ عندما نستخدم إطار عمل Executer.

ExecutorService هي واجهة فرعية لـ Executor, ، والذي يقبل كلاً من المهام القابلة للتشغيل والقابلة للاستدعاء.

يمكن تحقيق خيوط متعددة سابقة باستخدام Interface Runnableمنذ 1.0, لكن المشكلة هنا هي أنه بعد إكمال مهمة الموضوع، لا نتمكن من جمع معلومات المواضيع.من أجل جمع البيانات قد نستخدم الحقول الثابتة.

مثال: سلاسل منفصلة لجمع بيانات كل طالب.

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

لحل هذه المشكلة قدموا Callable<V>منذ 1.5 والتي ترجع نتيجة وقد تطرح استثناءً.

  • طريقة مجردة واحدة :تحتوي كل من واجهة Callable وRunnable على طريقة مجردة واحدة، مما يعني أنه يمكن استخدامها في تعبيرات lambda في 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().

مثال على استخدام الواجهات القابلة للتشغيل والقابلة للاستدعاء مع إطار عمل Executor.

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();
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top