تاريخيًا، حاولت Java توفير مقاطعات وقائية محدودة، ولكن كانت هناك العديد من المشكلات، مثل Thread.stop وThread.suspend وThread.resume التي تم تقديمها سابقًا. من ناحية أخرى، نظرًا لمتانة كود تطبيق Java، تم تخفيض عتبة البرمجة وتقليل احتمال قيام المبرمجين الذين لا يعرفون الآلية الأساسية بإتلاف النظام عن غير قصد.
اليوم، لا توفر جدولة سلاسل المحادثات في Java مقاطعات وقائية، ولكنها تستخدم المقاطعات التعاونية. في الواقع، مبدأ المقاطعة التعاونية بسيط للغاية، وهو استطلاع علامة معينة تشير إلى انقطاع، ويمكننا تنفيذه في أي كود عادي.
على سبيل المثال الكود التالي:
يتم مقاطعة المنطق المتطاير؛
//...
بينما (! يتم المقاطعة) {
حساب();
}
ومع ذلك، فإن مشاكل التعليمات البرمجية المذكورة أعلاه واضحة أيضًا. عندما يكون وقت تنفيذ الحساب طويلاً نسبيًا، لا يمكن الاستجابة للمقاطعة في الوقت المناسب. من ناحية أخرى، باستخدام الاستقصاء للتحقق من متغيرات العلامة، لا توجد طريقة لمقاطعة عمليات حظر مؤشر الترابط مثل الانتظار والسكون.
إذا كنت لا تزال تستخدم الفكرة المذكورة أعلاه، إذا كنت تريد الاستجابة للمقاطعة في الوقت المناسب، فيجب عليك التحقق من متغير العلامة من خلال جدولة الخيط في الجزء السفلي من الجهاز الظاهري. نعم، يتم ذلك بالفعل في JVM.
ما يلي مقتطف من الكود المصدري لـ java.lang.Thread:
مقاطعة منطقية ثابتة عامة () {
إرجاع currentThread().isInterrupted(true);
}
//...
يتم مقاطعة منطقية أصلية خاصة (boolean ClearInterrupted)؛
يمكن العثور على أن isInterrupted تم الإعلان عنه كطريقة أصلية، والتي تعتمد على التنفيذ الأساسي لـ JVM.
في الواقع، يحتفظ JVM بعلامة مقاطعة داخليًا لكل مؤشر ترابط. ومع ذلك، لا يمكن للتطبيق الوصول مباشرة إلى متغير المقاطعة هذا ويجب أن يعمل من خلال الطرق التالية:
موضوع الطبقة العامة {
// تعيين علامة المقاطعة
مقاطعة الفراغ العام () { ... }
// احصل على قيمة علامة المقاطعة
يتم مقاطعة المنطقية العامة () { ... }
// امسح علامة المقاطعة وأرجع قيمة علامة المقاطعة الأخيرة
مقاطعة منطقية ثابتة عامة () { ... }
}
عادةً، لا يؤدي استدعاء أسلوب المقاطعة لخيط ما إلى حدوث مقاطعة على الفور، ولكنه يقوم فقط بتعيين علامة المقاطعة داخل JVM. لذلك، من خلال التحقق من إشارة المقاطعة، يمكن للتطبيق القيام بشيء خاص أو تجاهل المقاطعة تمامًا.
قد تعتقد أنه إذا كان JVM يوفر آلية المقاطعة الأولية هذه فقط، فلن يكون له أي ميزة مقارنة بطريقة التطبيق الخاصة لتحديد متغيرات المقاطعة والاستقصاء.
الميزة الرئيسية لمتغيرات المقاطعة الداخلية لـ JVM هي أنها توفر آلية لمحاكاة "مصائد المقاطعة" التلقائية في مواقف معينة.
عند تنفيذ مكالمات الحظر التي تتضمن جدولة مؤشر الترابط (مثل الانتظار والسكون والانضمام)، في حالة حدوث انقطاع، سيقوم الخيط المحظور بطرح InterruptedException "في أسرع وقت ممكن". لذلك، يمكننا استخدام إطار التعليمات البرمجية التالي للتعامل مع المقاطعات التي تحظر مؤشر الترابط:
يحاول {
// انتظر أو نم أو انضم
}
قبض على (InterruptedException ه) {
// بعض أعمال معالجة المقاطعة
}
من خلال "أسرع ما يمكن"، أعتقد أن JVM يتحقق من متغير المقاطعة في الفجوة بين جدولة الخيط، وتعتمد السرعة على تنفيذ JVM وأداء الأجهزة.
ومع ذلك، بالنسبة لبعض عمليات حظر سلاسل الرسائل، لا يقوم JVM تلقائيًا بطرح InterruptedException. على سبيل المثال، بعض عمليات الإدخال/الإخراج وعمليات القفل الداخلي. بالنسبة لهذا النوع من العمليات، يمكن محاكاة المقاطعات بطرق أخرى:
1) مأخذ الإدخال/الإخراج غير المتزامن في java.io
عند قراءة وكتابة المقابس، ستحظر أساليب القراءة والكتابة الخاصة بـ InputStream وOutputStream وتنتظر، ولكنها لن تستجيب لمقاطعات Java. ومع ذلك، بعد استدعاء طريقة إغلاق المقبس، سيطرح الخيط المحظور استثناء المقبس.
2) تم تنفيذ الإدخال/الإخراج غير المتزامن باستخدام المحدد
إذا تم حظر مؤشر الترابط في Selector.select (في java.nio.channels)، فإن استدعاء طريقة التنبيه سيؤدي إلى استثناء ClosedSelectorException.
3) اكتساب القفل
إذا كان هناك خيط ينتظر الحصول على قفل داخلي، فلا يمكننا مقاطعته. ومع ذلك، باستخدام طريقة lockInterruptible لفئة Lock، يمكننا توفير إمكانيات المقاطعة أثناء انتظار القفل.
بالإضافة إلى ذلك، في إطار عمل يتم فيه فصل المهام وسلاسل العمليات، لا تعرف المهمة عادةً أي خيط سيتم استدعاؤه من خلاله، وبالتالي لا تعرف استراتيجية مؤشر الترابط المستدعي للتعامل مع الانقطاعات. لذلك، بعد أن تقوم المهمة بتعيين علامة مقاطعة مؤشر الترابط، ليس هناك ضمان بإلغاء المهمة. لذلك هناك مبدأين للبرمجة:
1) لا ينبغي عليك مقاطعة أي موضوع إلا إذا كنت تعرف سياسة المقاطعة الخاصة به.
يخبرنا هذا المبدأ أنه لا ينبغي لنا استدعاء طريقة المقاطعة لسلسلة رسائل مباشرة في إطار عمل مثل Executer، ولكن يجب علينا استخدام طرق مثل Future.cancel لإلغاء المهام.
2) يجب ألا يخمن رمز المهمة ما تعنيه المقاطعة لسلسلة التنفيذ.
يخبرنا هذا المبدأ أنه عندما يواجه الكود العام InterruptedException، فلا يجب أن يلتقطه و"يبتلعه"، بل يجب أن يستمر في رميه إلى الكود العلوي.
باختصار، تتطلب آلية المقاطعة غير الوقائية في Java تغيير فكرة المقاطعة الوقائية التقليدية واعتماد مبادئ وأنماط مقابلة للبرمجة بناءً على فهم جوهرها.