في المقالة السابقة "سلسلة Java Concurrency Series [1] ----- تحليل رمز مصدر المصدر الملخصي" ، قدمنا بعض المفاهيم الأساسية لـ AbstractQueuedSynchronizer ، نتحدث بشكل رئيسي عن كيفية تنفيذ منطقة قائمة الانتظار في AQS ، وما هو الوضع الحصري والوضع المشترك ، وكيفية فهم حالة انتظار العقد. إن فهم هذه المحتويات وإتقانها هو مفتاح القراءة اللاحقة لرمز مصدر AQS ، لذلك يوصى بأن يقرأ القراء مقالتي السابقة أولاً ثم انظر إلى هذه المقالة لفهمها. في هذه المقالة ، سنقدم كيف تدخل العقد قائمة انتظار قائمة انتظار المزامنة في الوضع الحصري وما هي العمليات التي سيتم تنفيذها قبل مغادرة قائمة انتظار المزامنة. يوفر AQS ثلاث طرق للحصول على أقفال في الوضع الحصري والوضع المشترك: الاستحواذ على مقاطعة مؤشرات الترابط غير المستجيبة ، واكتساب مؤشر ترابط الاستجابة ، وضبط المهلة. الخطوات العامة لهذه الأساليب الثلاث متشابهة تقريبًا ، مع عدد قليل من الأجزاء المختلفة ، لذلك إذا فهمت طريقة واحدة ونظرت إلى تنفيذ طرق أخرى ، فستكون متشابهًا. في هذه المقالة ، سأركز على طريقة الاستحواذ لعدم الاستجابة لمقاطعات الخيط ، وستتحدث الطريقتان الأخريان أيضًا عن التناقضات.
1. كيفية الحصول على أقفال مع مقاطعات الخيط غير المستجيبة؟
// لا تستجيب لاكتساب طريقة المقاطعة (الوضع الحصري) الحصول على الفراغ النهائي العام (int arg) {if (! tryacquire (arg) && quiperqueued (addwaiter (node.exclusive) ، arg)) {selfinterrupt () ؛ }}على الرغم من أن الكود أعلاه يبدو بسيطًا ، إلا أنه يؤدي الخطوات الأربع الموضحة في الشكل أدناه بالترتيب. أدناه سوف نوضح ونحلل خطوة بخطوة.
الخطوة 1:! Tryacquire (Arg)
// حاول الحصول على القفل (الوضع الحصري) المحمي Boolean TryAcquire (int arg) {رمي جديد UnsupportedOperationException () ؛}في هذا الوقت ، جاء شخص ما وحاول أن يطرق الباب أولاً. إذا وجد أن الباب لم يكن مغلقًا (TryAcquire (Arg) = True) ، فسيذهب مباشرة. إذا وجدت أن الباب مغلق (TryAcquire (Arg) = false) ، فأداء الخطوة التالية. تحدد طريقة TryAcquire عند فتح القفل وعندما يتم إغلاق القفل. يجب كتابة هذه الطريقة بواسطة الفئات الفرعية وإعادة كتابة منطق الحكم في الداخل.
الخطوة 2: AddWaiter (Node.Exclusive)
// لف مؤشر الترابط الحالي في عقدة وأضفه إلى ذيل قائمة انتظار المزامنة الخاصة بـ AddWaiter (وضع العقدة) {// حدد الوضع الذي يحمل عقدة القفل = node node (thread.currentThread () ، الوضع) ؛ // احصل على مرجع عقدة الذيل لعقدة قائمة انتظار المزامنة قبل ذيل ؛ // إذا لم تكن عقدة الذيل فارغة ، فهذا يعني أن قائمة انتظار المزامنة تحتوي بالفعل على عقدة إذا (pred! = null) {// 1. أشر إلى عقدة الذيل الحالية. prev = pred ؛ // 2. اضبط العقدة الحالية على عقدة الذيل if (CompareAndSetTail (pred ، node)) {// 3. أشر إلى خليفة عقدة الذيل القديمة إلى عقدة الذيل الجديدة pred.next = node ؛ عقدة العودة. }} // خلاف ذلك ، فهذا يعني أن قائمة انتظار المزامنة لم تتم تهيئة ENQ (العقدة) ؛ Return Node ؛} // Node Enqueue Private Node (Node Node Final) {for (؛؛) {// احصل على مرجع عقدة الذيل لعقدة قائمة انتظار التزامن t = tail ؛ // إذا كانت عقدة الذيل فارغة ، فهذا يعني أنه لم يتم تهيئة قائمة انتظار المزامنة إذا (t == null) {// تهيئة قائمة انتظار المزامنة إذا (المقارنات (node ())) {tail = head ؛ }} آخر {// 1. أشر إلى عقدة الذيل الحالية. // 2. اضبط العقدة الحالية على عقدة الذيل if (CompareAndSetTail (t ، node)) {// 3. أشر على خليفة عقدة الذيل القديمة إلى عقدة الذيل الجديدة T.Next = Node ؛ العودة ر ؛ }}}}يشير التنفيذ إلى هذه الخطوة إلى أنه في المرة الأولى التي فشل فيها عملية الاستحواذ على القفل ، فإن الشخص سيحصل على بطاقة أرقام لنفسه ويدخل منطقة قائمة الانتظار في قائمة الانتظار. عند تلقي بطاقة الأرقام ، سيعلن كيف يريد شغل الغرفة (الوضع الحصري أو وضع المشاركة). لاحظ أنه لم يجلس ويستريح في هذا الوقت (شنق نفسه).
الخطوة 3: اكتساب (addwaiter (node.exclusive) ، arg)
// الحصول على القفل بطريقة غير متوقعة (الوضع الحصري) الاستحواذ النهائي المنطقي (عقدة العقدة النهائية ، int arg) {boolean favor = true ؛ حاول {boolean interrupt = false ؛ لـ (؛؛) {// احصل على مرجع العقدة السابقة للعقدة النهائية المعطاة p = node.predecessor () ؛ // إذا كانت العقدة الحالية هي العقدة الأولى لقائمة انتظار المزامنة ، فحاول الحصول على القفل إذا كان (p == Head && tryacquire (arg)) {// قم بتعيين العقدة المحددة مثل عقدة الرأس (العقدة) ؛ // للمساعدة في جمع القمامة ، قم بمسح خليفة عقدة الرأس السابقة P.Next = null ؛ // فشلت حالة الاستحواذ الناجحة = false ؛ // إرجاع الحالة المقطوعة ، يتم تنفيذ الحلقة بأكملها هنا ، وتوقف عودة الخروج ؛ } // وإلا فهذا يعني أن حالة القفل لا تزال غير متوفرة. في هذا الوقت ، حدد ما إذا كان يمكن تعليق مؤشر الترابط الحالي // إذا كانت النتيجة صحيحة ، يمكن تعليق الخيط الحالي ، وإلا فإن الحلقة ستستمر ، خلال هذه الفترة ، لن يستجيب مؤشر الترابط للمقاطعة إذا (يجب أن يكون (يجب أن يكون (يجب أن يكون هناك صقل ؛ }}} أخيرًا {// تأكد من إلغاء الاستحواذ إذا (فشل) {cancelAcquire (node) ؛ }}} // judge ما إذا كان بإمكانه تعليق العقدة الحالية static boolean shouldparkafterfailedacquire (عقدة pred ، عقدة العقدة) {// الحصول على حالة انتظار العقدة الأمامية int ws = pred.waitstatus ؛ // إذا كانت حالة العقدة الأمامية إشارة ، فهذا يعني أن العقدة الأمامية ستستيقظ على العقدة الحالية ، بحيث يمكن للعقدة الحالية تعليقها بأمان إذا (ws == node.signal) {return true ؛ } if (ws> 0) {// العملية التالية هي تنظيف جميع العقد الأمامية التي تم إلغاؤها في قائمة انتظار المزامنة do {node.prev = pred = pred.prev ؛ } بينما (pred.waitstatus> 0) ؛ pred.next = العقدة ؛ } آخر {// إلى هذه الغاية ، فهذا يعني أن حالة العقدة الأمامية ليست إشارة ، ومن المحتمل أن تكون مساوية لـ 0. وبهذه الطريقة ، لن تستيقظ العقدة الأمامية على العقدة الحالية. } return false ؛} // تعليق الخيط الحالي النهائي النهائي المنطقي parkandcheckinterrupt () {locksupport.park (this) ؛ return thread.Interrupted () ؛}بعد الحصول على علامة الرقم ، سوف ينفذ هذه الطريقة على الفور. عندما تدخل العقدة إلى منطقة قائمة الانتظار لأول مرة ، هناك حالتان. أحدهما هو أنه يجد أن الشخص الذي أمامه قد ترك مقعده ودخل الغرفة ، لذلك لن يجلس ويستريح ، وسيطير الباب مرة أخرى لمعرفة ما إذا كان الطفل قد تم. إذا حدث الشخص في الداخل ، فسوف يندفع دون الاتصال بنفسه. خلاف ذلك ، كان عليه أن يفكر في الجلوس والراحة لفترة من الوقت ، لكنه كان لا يزال قلقًا. ماذا لو لم يذكره أحد بعد أن جلس ونام؟ لقد ترك ملاحظة صغيرة على مقعد الرجل في المقدمة حتى يتمكن الشخص الذي خرج من الداخل من إيقاظه بعد رؤية الملاحظة. هناك موقف آخر هو أنه عندما دخل منطقة قائمة الانتظار ووجد أن هناك عدة أشخاص يصطفون أمامه ، كان بإمكانه الجلوس لفترة من الوقت ، ولكن قبل ذلك ، كان لا يزال يترك ملاحظة على مقر الشخص أمامه (كان نائماً بالفعل في هذا الوقت) حتى يتمكن الشخص من إيقاظه قبل مغادرته. عندما يتم كل شيء ، ينام بسلام. لاحظ أننا نرى أن For For Loop يحتوي على مخرج واحد فقط ، أي أنه لا يمكن أن يخرج إلا بعد أن يكتسب مؤشر الترابط القفل بنجاح. قبل الحصول على القفل ، يتم تعليقه دائمًا في طريقة ParkandCheckInterrupt () للحلقة. بعد إيقاظ الخيط ، يستمر أيضًا في تنفيذ الحلقة من هذا المكان.
الخطوة 4: الانفصال الذاتي ()
// سوف يقطع مؤشر الترابط الحالي نفسه خاصًا باطلبًا ثابتًا ذاتيًا () {thread.currentThRead (). }نظرًا لأن الخيط بأكمله أعلاه قد تم تعليقه في طريقة ParkandCheckinterrupt () للحلقة الخاصة ، فإنه لا يستجيب لأي شكل من أشكال مقاطعة مؤشر الترابط قبل الحصول عليه بنجاح. فقط عندما يحصل الخيط بنجاح على القفل ويخرج من الحلقة ، فسيتحقق مما إذا كان شخص ما يطلب مقاطعة الخيط خلال هذه الفترة. إذا كان الأمر كذلك ، اتصل بالطريقة SelfInterrupt () لتعليق نفسها.
2. كيفية الحصول على الأقفال استجابة لمقاطعات الموضوع؟
. فشل Boolean = صحيح ؛ حاول {for (؛؛) {// الحصول على العقدة النهائية السابقة p = node.predecessor () ؛ // إذا كانت P عبارة عن عقدة رأس ، فإن مؤشر الترابط الحالي يحاول الحصول على القفل مرة أخرى إذا (p == Head && TryAcquire (Arg)) {SetheAd (Node) ؛ p.next = null ؛ // فشل مساعدة GC = false ؛ // إرجاع الإرجاع بعد الحصول على القفل بنجاح ؛ } // إذا تم استيفاء الشرط ، فسيتم تعليق الخيط الحالي. في هذا الوقت ، يتم الرد على المقاطعة ويتم إلقاء استثناء إذا (يجب أن يكون (يجب أن يكون parkafterfailedacquire (P ، Node) && parkandcheckinterrupt ()) {// إذا تم إيقاظ الموضوع ، فسيتم طرح استثناء إذا تم العثور على طلب المقاطعة ، فسيتم إلقاء الفشل. رمي جديد interruptedException () ؛ }}} أخيرًا {if (فشل) {cancelAcquire (node) ؛ }}}طريقة مقاطعة مؤشر ترابط الاستجابة وطريقة مقاطعة مؤشر الترابط غير المستجيبة هي نفسها تقريبًا في عملية الحصول على الأقفال. الفرق الوحيد هو أنه بعد أن يستيقظ الخيط من طريقة ParkandCheckInterrupt ، سيتحقق مما إذا كان الخيط قد توقف. إذا كان الأمر كذلك ، فسوف يرمي استثناء مقاطع. بدلاً من الاستجابة لقفل الاستحواذ على مقاطعة مؤشرات الترابط ، فإنه يحدد حالة المقاطعة فقط بعد تلقي طلب المقاطعة ، ولن ينهي على الفور الطريقة الحالية للحصول على القفل. لن يقرر ما إذا كان سيتم تعليق نفسه بناءً على حالة المقاطعة بعد أن تحصل العقدة على القفل بنجاح.
3. كيف تضع وقت المهلة لاكتساب القفل؟
// الحصول على القفل مع مهلة محدودة (الوضع الحصري) doacquirenanos المنطقي الخاص (int arg ، nanostimeout الطويل) يلقي interruptedException {// الحصول على وقت النظام الحالي طويل الوقت = system.nanotime () ؛ // لف مؤشر الترابط الحالي في عقدة وإضافته إلى Node Final Queue Node = AddWaiter (node.exclusive) ؛ فشل Boolean = صحيح ؛ حاول {for (؛؛) {// الحصول على العقدة النهائية السابقة p = node.predecessor () ؛ // إذا كانت العقدة السابقة عبارة عن عقدة رأس ، فإن مؤشر الترابط الحالي يحاول الحصول على القفل مرة أخرى إذا كان (p == Head && tryacquire (arg)) {// تحديث عقدة الرأس (العقدة) ؛ p.next = null ؛ فشل = خطأ ؛ العودة صحيح. } // بمجرد استخدام وقت المهلة ، قم بالخروج من الحلقة مباشرة إذا (nanostimeout <= 0) {return false ؛ } // إذا كان وقت المهلة أكبر من وقت الدوران ، ثم بعد الحكم على أنه يمكن تعليق الخيط ، فسيتم تعليق الموضوع لفترة من الوقت إذا (يجب أن يكون (يجب أن يكون (parkafterfailedacquire (p ، node) && nanostimeout> الدوار } // احصل على الوقت الحالي للنظام طويل الآن = System.Nanotime () ؛ // يتم طرح وقت المهلة من الفاصل الزمني لقفل الاستحواذ nanostimeout - = الآن - آخر مرة ؛ // تحديث آخر مرة مرة أخرى في الوقت الحالي = الآن ؛ // يتم طرح الاستثناء عند استلام طلب المقاطعة أثناء عملية الاستحواذ على القفل إذا (thread.interrupted ()) {رمي جديد interruptedException () ؛ }}} أخيرًا {if (فشل) {cancelAcquire (node) ؛ }}}سيحصل ضبط وقت المهلة على القفل أولاً. بعد فشل الاستحواذ في المرة الأولى ، سوف يعتمد على الموقف. إذا كان وقت المهلة الوارد أكبر من وقت الدوران ، فسيتم تعليق الخيط لفترة من الوقت ، وإلا فإنه سيكون يدور. بعد كل مرة يتم فيها الحصول على القفل ، سيتم طرح وقت المهلة من الوقت المستغرق للحصول على القفل. حتى تكون المهلة أقل من 0 ، فهذا يعني أن وقت المهلة قد تم استخدامه. بعد ذلك ، سيتم إنهاء تشغيل القفل وسيتم إرجاع علامة فشل الاستحواذ. لاحظ أنه أثناء عملية الحصول على القفل مع وقت المهلة ، يمكنك الرد على طلبات مقاطعة مؤشرات الترابط.
4. كيف يطلق الخيط القفل ويترك قائمة انتظار المزامنة؟
// عملية القفل الإفراج (الوضع الحصري) إصدار Boolean النهائي العام (int arg) {// اقلب قفل كلمة المرور لمعرفة ما إذا كان يمكن إلغاء قفل إذا كان (tryRelease (Arg)) {// الحصول على عقدة الرأس H = Head ؛ // إذا لم تكن عقدة الرأس فارغة ولم تكن حالة الانتظار مساوية لـ 0 ، فقم باستيقاظ عقدة الخلف إذا (h! = null && } إعادة صواب ؛ } return false ؛} // استيقظ على عقدة الخلف الخاصة unparksuccessor (عقدة العقدة) {// احصل على حالة انتظار العقدة المعطاة int ws = node.waitstatus ؛ // قم بتحديث حالة الانتظار إلى 0 إذا (ws <0) {compareAndSetWaitStatus (العقدة ، WS ، 0) ؛ } // احصل على العقدة اللاحقة للعقدة المعطاة s = node.next ؛ // عقدة الخلف فارغة أو يتم إلغاء حالة الانتظار إذا (s == null || s.waitstatus> 0) {s = null ؛ // الانتهاء من العقدة الأولى التي لم يتم إلغاؤها من قائمة انتظار التجوال المتخلفة إلى (العقدة t = tail ؛ t! = null && t! = node ؛ t = t.prev) {if (t.waitstatus <= 0) {s = t ؛ }}} // استيقظ العقدة الأولى بعد عقدة معينة ليست حالة إلغاء إذا (s! = null) {locksupport.unpark (s.Thread) ؛ }}بعد أن يحمل الخيط القفل في الغرفة ، سيفعل أعماله الخاصة. بعد الانتهاء من العمل ، سيصدر القفل ويترك الغرفة. يمكن إلغاء قفل كلمة المرور من خلال طريقة TryRelease. نحن نعلم أن طريقة TryRelease تحتاج إلى الكتابة فوق الفئة الفرعية. تختلف قواعد التنفيذ الخاصة بالفئات الفرعية المختلفة ، مما يعني أن كلمات المرور التي تم تعيينها بواسطة فئات فرعية مختلفة مختلفة. على سبيل المثال ، في Reentrantlock ، في كل مرة يتصل فيها الشخص في الغرفة بطريقة TryRelease ، سيتم تخفيض الحالة بمقدار 1 ، حتى يتم تخفيض الحالة إلى 0 ، سيتم فتح قفل كلمة المرور. فكر فيما إذا كانت هذه العملية تبدو وكأننا ندير عجلة قفل كلمة المرور باستمرار ، ويتم تقليل عدد العجلات بمقدار 1 في كل مرة نديرها. يشبه CountDownlatch هذا الأمر إلى حد ما ، إلا أنه ليس فقط أنه يدير شخصًا واحدًا ، ولكنه سيؤدي إلى قلب شخص واحد ، ويركز قوة الجميع على فتح القفل. بعد أن يغادر الخيط الغرفة ، سيجد مقعده الأصلي ، أي العثور على عقدة الرأس. معرفة ما إذا كان أي شخص قد ترك ملاحظة صغيرة لها على المقعد. إذا كان هناك ، فسيعلم أن شخصًا ما نائم ويحتاج إلى طلب المساعدة في إيقاظه ، وبعد ذلك سوف يستيقظ هذا الموضوع. إذا لم يكن الأمر كذلك ، فهذا يعني أنه لا يوجد أحد ينتظر في قائمة انتظار المزامنة في الوقت الحالي ، ولا يحتاج أحد إلى الاستيقاظ ، لذلك يمكن أن يترك راحة البال. العملية أعلاه هي عملية إطلاق القفل في الوضع الحصري.
ملاحظة: يعتمد جميع التحليلات المذكورة أعلاه على JDK1.7 ، وستكون هناك اختلافات بين الإصدارات المختلفة ، يحتاج القراء إلى الانتباه.
ما سبق هو كل محتوى هذه المقالة. آمل أن يكون ذلك مفيدًا لتعلم الجميع وآمل أن يدعم الجميع wulin.com أكثر.