الهدف من اتصال الخيط هو تمكين المواضيع من إرسال إشارات إلى بعضها البعض. من ناحية أخرى ، يمكّن اتصال الخيط المواضيع من انتظار إشارات من مؤشرات الترابط الأخرى.
الاتصال عبر الأشياء المشتركة
تتمثل إحدى الطرق السهلة لإرسال الإشارات بين مؤشرات الترابط في تعيين قيمة الإشارة في متغيرات الكائن المشترك. الخيط A يعين المعالجة المتغيرة لعضو Boolean إلى True في كتلة التزامن ، ويقرأ مؤشر الترابط B أيضًا متغير عضو المعالجة في كتلة التزامن. يستخدم هذا المثال البسيط كائنًا يحمل إشارة ويوفر أساليب المجموعة والتحقق:
الطبقة العامة mysignal {محمية boolean hasdatatoProcess = false ؛ العلاقة المنطقية المزامنة العامة () } sethasDatatoProcess الفراغ المتزامن العام (boolean hasdata) {this.hasdatatoprocess = hasdata ؛ }}يجب أن تحصل المواضيع A و B على إشارة إلى مثيل مشترك mysignal للتواصل. إذا كانت المراجع التي يحتفظون بها تشير إلى مثيلات مختلفة ، فلن يتمكن بعضهم البعض من اكتشاف إشارات بعضهم البعض. يمكن تخزين البيانات المراد معالجتها في منطقة ذاكرة التخزين المؤقت المشتركة ، والتي يتم تخزينها بشكل منفصل عن مثيل mysignal.
انتظر مشغول
الموضوع B الذي يستعد لمعالجة البيانات تنتظر أن تصبح البيانات متاحة. بمعنى آخر ، إنه ينتظر إشارة من الخيط A ، مما يؤدي إلى العودة إلى المعالجة () بشكل صحيح. يعمل الموضوع B في حلقة لانتظار هذه الإشارة:
MySignal محمي مشترك = ...... بينما (! sharedsignal.hasdatoProcess ()) {// لا شيء ... مشغول الانتظار}انتظر () ، إخطار () وإخطار () ()
لا يستخدم الانتظار المشغول بشكل فعال وحدة المعالجة المركزية التي تقوم بتشغيل موضوع الانتظار ما لم يكن متوسط وقت الانتظار قصيرًا جدًا. خلاف ذلك ، من الحكمة جعل مؤشر ترابط الانتظار نومًا أو عدم الركود حتى يتلقى الإشارة التي تنتظرها.
لدى Java آلية انتظار مدمجة للسماح لخيوط أن تصبح غير متوفرة أثناء انتظار الإشارات. تحدد فئة java.lang.object ثلاث طرق ، wait () ، إخطار () وإخطار () ، لتنفيذ آلية الانتظار هذه.
بمجرد استدعاء مؤشر ترابط طريقة WAIT () لأي كائن ، يصبح حالة غير متوفرة حتى يقوم مؤشر ترابط آخر باستدعاء طريقة الإخطار () لنفس الكائن. من أجل استدعاء WAIT () أو إخطار () ، يجب أولاً الحصول على قفل هذا الكائن. وهذا هو ، يجب أن يدعو الموضوع wait () أو إخطار () في كتلة التزامن. فيما يلي نسخة معدلة من mysingal - mywaitnotify باستخدام Wait () و Amply ():
فئة عامة مراقبة {} الفئة العامة mywaitnotify {monitorObject myMonitorObject = new monitorObject () ؛ public void dowait () {synchronized (myMonitorObject) {try {myMonitorObject.wait () ؛ } catch (interruptedException e) {...}}} public void donotify () {synchronized (myMonitorObject) {myMonitorObject.Notify () ؛ }}}سيقوم مؤشر ترابط الانتظار بالاتصال بـ Dowait () ، بينما سيتصل موضوع Wake-Up Donotify (). عندما يستدعي مؤشر ترابط طريقة الإخطار () لكائن ما ، سيتم إيقاظ أحد مؤشرات الترابط التي تنتظر الكائن والسماح لها بالتنفيذ (ملاحظة: يتم إيقاظ هذا الموضوع عشوائيًا ، ولا يمكن تحديده أي مؤشر ترابط يستيقظ). كما يتم توفير طريقة إعلام () لإيقاظ جميع مؤشرات الترابط في انتظار كائن معين.
كما ترون ، ما إذا كان مؤشر ترابط الانتظار أو مؤشر ترابط الاستيقاظ ، فإنه يستدعي Wait () وإخطار () في كتلة التزامن. هذا إلزامي! إذا لم يحمل مؤشر ترابط قفل الكائن ، فلا يمكن استدعاء Wait () أو إخطار () أو إخطار (). خلاف ذلك ، سيتم طرح استثناء غير شرعي.
(ملاحظة: هذه هي الطريقة التي يتم بها تنفيذ JVM. عند الاتصال بالانتظار ، يتحقق أولاً مما إذا كان الخيط الحالي هو مالك القفل ، ويرمي غير unshalalmonitorStateExcept.)
ولكن كيف هذا ممكن؟ عند انتظار تنفيذ مؤشر الترابط في كتلة التزامن ، ألا يحتفظ دائمًا بقفل كائن الشاشة (كائن MyMonitor)؟ هل يمكن لخيط الانتظار حظر خيط الاستيقاظ الذي يدخل كتلة متزامنة من Donotify ()؟ الجواب هو: هذا صحيح. بمجرد استدعاء مؤشر الترابط طريقة Wait () ، فإنه يطلق القفل على كائن الشاشة المعمول به. سيسمح هذا لخيوط أخرى بالاتصال WAIT () أو إخطار () أيضًا.
بمجرد استيقاظ مؤشر الترابط ، لا يمكن الخروج على اتصال طريقة Wait () على الفور حتى يتم استدعاء الإخطار ().
الطبقة العامة mywaitnotify2 {monitorObject myMonitorObject = new MonitorObject () ؛ wassignaled boolean = false ؛ public void dowait () {synchronized (myMonitorObject) {if (! wassignaled) {try {myMonitorObject.wait () ؛ } catch (interruptedException e) {...}} // clear signal and onte recly. wassignaled = false ؛ }} public void donotify () {synchronized (myMonitorObject) {wasSignaled = true ؛ myMonitorObject.Notify () ؛ }}}
يخرج الخيط كتلة التزامن الخاصة به. بمعنى آخر ، يجب أن يستعيد مؤشر الترابط الاستيقاظ قفل كائن الشاشة قبل أن يتمكن من الخروج من استدعاء طريقة Wait () ، لأن طريقة استدعاء طريقة الانتظار تعمل في كتلة التزامن. إذا تم إيقاظ مؤشرات ترابط متعددة بواسطة AmplyAll () ، فعندئذٍ في نفس الوقت ، يمكن لخيط واحد فقط الخروج من طريقة Wait () ، لأن كل مؤشر ترابط يجب أن يحصل على قفل كائن الشاشة قبل الخروج من الانتظار ().
الإشارات الفائتة
لا تحفظ أساليب الإخطار () و AmplyAll () الطريقة التي تستدعيها ، لأنه عندما يتم استدعاء هاتين الطريقتين ، فمن الممكن عدم وجود مؤشر ترابط في حالة الانتظار. تم التخلص من إشارة الإخطار. لذلك ، إذا استدعى مؤشر ترابط الإخطار () قبل إخطاره قبل الاتصال بالاتصال () ، فإن مؤشر ترابط الانتظار سيغيب عن هذه الإشارة. قد يكون هذا أو لا يمثل مشكلة. ومع ذلك ، في بعض الحالات ، قد يجعل هذا موضوع الانتظار ينتظر دائمًا ولم يعد يستيقظ لأن الخيط يفتقد إشارة الاستيقاظ.
لتجنب فقدان الإشارات ، يجب حفظها في فئة الإشارة. في مثال MyWaitNotify ، يجب تخزين إشارة الإخطار في متغير عضو في مثيل mywaitnotify. فيما يلي نسخة معدلة من mywaitnotify:
الطبقة العامة mywaitnotify2 {monitorObject myMonitorObject = new MonitorObject () ؛ wassignaled boolean = false ؛ public void dowait () {synchronized (myMonitorObject) {if (! wassignaled) {try {myMonitorObject.wait () ؛ } catch (interruptedException e) {...}} // clear signal and onte recly. wassignaled = false ؛ }} public void donotify () {synchronized (myMonitorObject) {wasSignaled = true ؛ myMonitorObject.Notify () ؛ }}}لاحظ أن طريقة Donotify () تحدد المتغير wassignalled إلى True قبل استدعاء Notify (). في الوقت نفسه ، لاحظ أن طريقة dowait () تتحقق من المتغير wassignaled قبل استدعاء wait (). في الواقع ، إذا لم يتم استلام أي إشارة خلال الفترة الزمنية بين مكالمة dowait () السابقة ودعوة dowait () ، فستتصل فقط بالاتصال ().
(ملاحظة إثبات: لتجنب فقدان الإشارة ، استخدم متغيرًا لحفظ ما إذا كان قد تم إخطاره. قبل الإخطار ، قم بإخطار نفسك.
استيقظ مزيف
لسبب ما ، من الممكن أن يستيقظ مؤشر الترابط دون استدعاء الإخطار () وإخطار (). وهذا ما يسمى الاستيقاظ الزائفة. استيقظ دون سبب.
في حالة حدوث إيقاظ زائف في طريقة dowait () لـ mywaitnotify2 ، يمكن لخيط الانتظار إجراء عمليات لاحقة حتى لو لم يتلق الإشارة الصحيحة. هذا يمكن أن يسبب مشاكل خطيرة في طلبك.
لمنع الاستيقاظ الخاطئ ، سيتم فحص متغيرات الأعضاء التي تحمل الإشارة في حلقة من الوقت ، وليس في التعبير if. تسمى مثل هذه الحلقة الوقت قفل الدوران (ملاحظة: يجب أن يكون هذا النهج حذرًا. يستهلك تطبيق JVM الحالي وحدة المعالجة المركزية. إذا لم يتم استدعاء طريقة Donotify لفترة طويلة ، فسوف تدور طريقة Dowait بشكل مستمر ، وسوف تستهلك وحدة المعالجة المركزية أكثر من اللازم). سوف يدور الخيط المستيقظ حتى تصبح الحالة الموجودة في قفل الدوران (بينما الحلقة) خاطئة. يوضح الإصدار المعدل التالي من mywaitnotify2 هذا:
الطبقة العامة mywaitnotify3 {monitorObject myMonitorObject = new MonitorObject () ؛ wassignaled boolean = false ؛ public void dowait () {synchronized (myMonitorObject) {بينما (! wassignaled) {try {myMonitorObject.wait () ؛ } catch (interruptedException e) {...}} // clear signal and onte recly. wassignaled = false ؛ }} public void donotify () {synchronized (myMonitorObject) {wasSignaled = true ؛ myMonitorObject.Notify () ؛ }}}لاحظ أن طريقة WAIT () في حلقة بينما ، وليس في التعبير if. إذا استيقظ مؤشر ترابط الانتظار دون استلام الإشارة ، فسيصبح المتغير الذي تم تصميمه خطأ ، وسيتم تنفيذ الحلقة مرة أخرى ، مما دفع مؤشر ترابط الاستيقاظ للعودة إلى حالة الانتظار.
سلاسل متعددة تنتظر نفس الإشارة
إذا كان لديك عدة مؤشرات ترابط تنتظر وتوقظ بواسطة AmplyAll () ، ولكن يُسمح فقط بمواصلة التنفيذ ، فإن استخدام حلقة الوقت أمر جيد أيضًا. يمكن لخيط واحد فقط الحصول على قفل كائن الشاشة في كل مرة ، مما يعني أن مؤشر ترابط واحد فقط يمكنه الخروج من استدعاء Wait () ومسح العلم wassignaled (تعيين على خطأ). بمجرد خروج هذا الخيط من كتلة التزامن dowait () ، تخرج مؤشرات الترابط الأخرى من استدعاء Wait () وتحقق من قيمة متغير wassignaled في الحلقة. ومع ذلك ، تم مسح هذا العلم من خلال الخيط الأول المستيقظ ، وبالتالي فإن بقية الخيوط المسكونة ستعود إلى حالة الانتظار حتى تصل الإشارة في المرة التالية.
لا تستدعي الانتظار () في ثوابت السلسلة أو الكائنات العالمية
(ملاحظة إثبات: تشير ثابت السلسلة المذكورة في هذا الفصل إلى المتغيرات ذات القيم الثابتة)
يستخدم إصدار سابق من هذه المقالة ثوابت السلسلة ("") ككائن أنبوب في مثال MyWaitNotify. هذا هو المثال:
الفئة العامة mywaitnotify {string mymonitorObject = "" ؛ wassignaled boolean = false ؛ public void dowait () {synchronized (myMonitorObject) {بينما (! wassignaled) {try {myMonitorObject.wait () ؛ } catch (interruptedException e) {...}} // clear signal and onte recly. wassignaled = false ؛ }} public void donotify () {synchronized (myMonitorObject) {wasSignaled = true ؛ myMonitorObject.Notify () ؛ }}}المشكلة الناجمة عن استدعاء WAIT () وإخطار () في كتلة التزامن في سلسلة فارغة كقفل (أو سلسلة ثابتة أخرى) هي أن JVM/برنامج التحويل البرمجي سيقوم بتحويل السلسلة الثابتة إلى نفس الكائن. هذا يعني أنه حتى لو كان لديك مثالان مختلفان mywaitnotify ، فإنهم يشيرون جميعًا إلى نفس مثيل السلسلة الفارغة. وهذا يعني أيضًا أن هناك خطرًا أن يوقظ مؤشر الترابط dowait () على مثيل MyWaitNotify الأول من خلال مؤشر الترابط Donotify () على مثيل mywaitnotify الثاني. يمكن رسم هذا الموقف على النحو التالي:
في البداية قد لا يكون هذا مشكلة كبيرة. بعد كل شيء ، إذا تم استدعاء Donotify () على مثيل MyWaitNotify الثاني ، فإن ما يحدث حقًا هو أن الخيوط A و B مستيقظين بشكل خاطئ. سيقوم مؤشر ترابط الاستيقاظ (A أو B) بالتحقق من قيمة الإشارة في الحلقة ثم العودة إلى حالة الانتظار ، لأنه لا يتم استدعاء Donotify () على مثيل MyWaitNotify الأول ، وهذا هو المثيل الذي ينتظره. هذا الموقف يعادل إثارة الصحوة الخاطئة. يستيقظ الموضوع A أو B دون تحديث قيمة الإشارة. لكن الكود يتعامل مع هذا الموقف ، لذلك يعود الخيط إلى حالة الانتظار. تذكر ، حتى إذا كانت 4 مؤشرات ترابط Call Wait () وإخطار () على نفس مثيل السلسلة المشتركة ، فسيتم حفظ الإشارات الموجودة في dowait () و donotify () عن طريق 2 مثالين mywaitnotify على التوالي. قد تستيقظ مكالمة Donotify () على mywaitnotify1 خيط mywaitnotify2 ، ولكن سيتم حفظ قيمة الإشارة فقط في mywaitnotify1.
تكمن المشكلة في أنه نظرًا لأن Donotify () فقط يستدعي الإخطار () بدلاً من الإخطار () ، حتى لو كان هناك 4 مؤشرات ترابط تنتظر على نفس مثيل السلسلة (السلسلة الفارغة) ، يتم استيقاظ مؤشر ترابط واحد فقط. لذلك ، إذا تم إيقاف مؤشر الترابط A أو B بواسطة إشارة تم إرسالها إلى C أو D ، فسوف يتحقق من قيمة الإشارة الخاصة به لمعرفة ما إذا كانت أي إشارة تم استلامها ثم العودة إلى حالة الانتظار. لم يتم إيقاظ C ولا D للتحقق من قيمة الإشارة التي تلقوها بالفعل ، لذلك فقدت الإشارة. هذا الموقف يعادل مشكلة الإشارات المفقودة المذكورة أعلاه. تم إرسال C و D إلى الإشارة ، ولكن لا يمكن للرد على الإشارة.
إذا استدعاء طريقة Donotify () إخطار () بدلاً من الإخطار () ، فسيتم إيقاظ جميع مؤشرات الترابط الانتظار وسيتم فحص قيمة الإشارة بدورها. سيعود المواضيع A و B إلى حالة الانتظار ، ولكن خيط واحد فقط في C أو D يلاحظ الإشارة ويخرج من استدعاء طريقة dowait (). سيعود الآخر في C أو D إلى حالة الانتظار لأن مؤشر الترابط الذي حصل على الإشارة يقوم بمسح قيمة الإشارة (تعيين إلى خطأ) أثناء عملية الخروج من dowait ().
بعد قراءة الفقرة أعلاه ، قد تحاول استخدام إخطار () بدلاً من الإخطار () ، ولكن هذه فكرة سيئة قائمة على الأداء. عندما يتمكن مؤشر ترابط واحد فقط من الإشارة ، لا يوجد سبب لإيقاظ جميع الخيوط في كل مرة.
لذلك: في آلية Wait ()/etefy () ، لا تستخدم الكائنات العالمية ، ثوابت السلسلة ، إلخ. يجب استخدام الكائن الفريد المقابل. على سبيل المثال ، يحتوي كل مثيل من myWaitNotify3 على كائن شاشة خاص به بدلاً من استدعاء Wait ()/etefy () على سلسلة فارغة.
ما سبق هو المعلومات المتعلقة بتواصل Java Multi-Thorreading و Thread. سنستمر في إضافة المعلومات ذات الصلة في المستقبل. شكرا لك على دعمك لهذا الموقع!