بشكل عام ، تكون سرعة مهام الإنتاج أكبر من سرعة الاستهلاك. مسألة التفاصيل هي طول قائمة الانتظار ، وكيفية مطابقة سرعة الإنتاج والاستهلاك.
نموذج المنتج النموذجي المستهلك هو كما يلي:
للإنتاج العام أسرع من الاستهلاك. عندما يكون قائمة الانتظار ممتلئة ، لا نريد تجاهل أي مهمة أو عدم تنفيذها. . يتم إنشاء الحظر أيضًا.
علاوة على ذلك ، عندما يكون قائمة الانتظار فارغة ، لا يمكن للمستهلكين الحصول على المهمة ، يمكنهم الانتظار لفترة من الوقت. يوصى بالاتصال بالاتصال. وبهذه الطريقة ، عندما يتوقف المنتج بالفعل عن الإنتاج ، لن ينتظر المستهلكون بلا حدود.
لذلك ، يتم تنفيذ نموذج الإنتاج والاستهلاك الفعال الذي يدعم الحظر.
انتظر ، لأن JUC ساعدنا في تنفيذ تجمعات الخيوط ، لماذا ما زلنا بحاجة إلى استخدام هذه المجموعة من الأشياء؟ أليس أكثر ملاءمة لاستخدام ExecutorService مباشرة؟
دعونا نلقي نظرة على الهيكل الأساسي لـ ThreadPoolexecutor:
ولكن المشكلة هي أنه حتى لو كنت تحدد يدويًا عملية تنفيذ قائمة انتظار عند إنشاء ThreadPoolexecutor ، في الواقع ، عندما تكون قائمة الانتظار ممتلئة ، فإن طريقة التنفيذ لن تمنع.
نسخة الكود كما يلي:
تنفيذ الفراغ العام (الأمر runnable) {
if (command == null)
رمي nullpointerxception () جديدة ؛
if (poolsize> = corepoolsize ||! addifunderCorePoolSize (أمر)) {
if (runState == Running && workqueue.offer (command)) {
إذا (RunState! = Running || poolsize == 0)
ضمان queuedtaskhandled (command) ؛
}
وإلا
رفض (أمر) ؛
}
}
في هذا الوقت ، يجب القيام بشيء ما لتحقيق نتيجة: عندما يقدم المنتج مهمة ما ويكون قائمة الانتظار ممتلئة ، يمكن للمنتج حظرها وانتظار استهلاك المهمة.
المفتاح هو أنه في بيئة متزامنة ، لا يمكن الحكم على قائمة الانتظار الكاملة من قبل المنتج ، و threadpoolexecutor.getqueue (). الحجم () لا يمكن استدعاؤها لتحديد ما إذا كانت قائمة الانتظار ممتلئة.
في تنفيذ تجمع الخيوط ، عندما تكون قائمة الانتظار ممتلئة ، سيتم استدعاء RespectedExecutionHandler الذي تم تمريره أثناء البناء لرفض معالجة المهمة. التنفيذ الافتراضي هو Abortpolicy ، والذي يلقي مباشرة مرفوضا.
لن أخوض في العديد من استراتيجيات الرفض هنا. الاستهلاك.
نسخة الكود كما يلي:
فئة ثابتة عامة callerrunspolicy أدوات DesideDexecutionHandler {
/**
* ينشئ <tt> callerrunspolicy </tt>.
*/
callerrunspolicy العام () {}
/**
* ينفذ المهمة R في موضوع المتصل ، ما لم يكن المنفذ
* تم إغلاقه ، وفي هذه الحالة يتم التخلص من المهمة.
* param r المهمة التي تم تشغيلها المطلوبة لتنفيذها
* param e المنفذ يحاول تنفيذ هذه المهمة
*/
void public rejectedExecution (Runnable R ، Threadpoolexecutor e) {
if (! e.isshutdown ()) {
R.Run () ؛
}
}
}
ومع ذلك ، فإن هذه الاستراتيجية لها مخاطر مخفية. الاستمرار في إنتاج المهام.
بالإشارة إلى الأفكار المماثلة ، وأبسط طريقة ، يمكننا تحديد موقع DesjectedExecutionHandler مباشرة ، وتغييرها لاستدعاء blockingqueue.
نسخة الكود كما يلي:
جديد rejectedExecutionHandler () {
@تجاوز
void public rejectedExecution (Runnable R ، Threadpoolexecutor Executor) {
if (! executor.isshutdown ()) {
يحاول {
Executor.getqueue (). put (r) ؛
} catch (InterruptedException e) {
// لا ينبغي مقاطعة
}
}
}
} ؛
وبهذه الطريقة ، لم نعد بحاجة إلى الاهتمام بمنطق قائمة الانتظار والمستهلك.
بالمقارنة مع التصميم الأصلي ، يمكن أن تقلل هذه الطريقة من كمية التعليمات البرمجية ، ويمكنها تجنب العديد من المشكلات في البيئات المتزامنة. بالطبع ، يمكنك أيضًا استخدام وسائل أخرى ، مثل استخدام Semaphores كحدود للدخول عند تقديمها ، ولكن إذا كنت تريد فقط منع المنتج ، فسيبدو الأمر معقدًا.