من خلال التحليل في المقالة السابقة ، نعلم أن هناك ثلاث طرق لاكتساب الأقفال ذات الوضع الحصري ، وهي الحصول على دون مقاطعة مؤشر ترابط الاستجابة ، للحصول على مقاطعات مؤشر ترابط الاستجابة ، وللحصول على وقت المهلة. هناك أيضًا هذه الطرق الثلاث لاكتساب الأقفال في الوضع المشترك ، وهي نفسها في الأساس. إذا اكتشفنا طريقة واحدة ، فيمكننا فهم طرق أخرى بسرعة. على الرغم من أن رمز المصدر الملخصي exectiquenchronizer يحتوي على أكثر من ألف سطر ، إلا أنه يتكرر أيضًا عدة مرات ، لذلك لا ينبغي أن يخاف القراء في البداية. ما عليك سوى قراءةه بصبر وببطء ، ستفهمه بشكل طبيعي. في تجربتي الشخصية ، هناك العديد من الجوانب الأكثر أهمية التي يجب فهمها عند قراءة رمز مصدر الملخصات uccediqueedsynchronizer ، أي الفرق بين الوضع الحصري والوضع المشترك ، وحالة انتظار العقد ، وفهم قوائم الانتظار الشرطية. إذا فهمت هذه النقاط الرئيسية ، فستكون قراءة رمز المصدر اللاحق أسهل بكثير. بالطبع ، يتم تقديمها في مقالتي "Java Concurrency Series [1] ---- تحليل رمز مصدر المصدر الملخص" ، ويمكن للقراء التحقق من ذلك أولاً. تحلل هذه المقالة وضع المشاركة إلى ثلاث طرق لاكتساب الأقفال وطريقة واحدة لإطلاق الأقفال.
1. عدم الاستجابة لاكتساب مقاطعة الخيط
. حاول الحصول على القفل if (tryacquireshared (arg) <0) {// 2. إذا فشل الاستحواذ ، أدخل هذه الطريقة doacquireshared (arg) ؛ }} // حاول الحصول على القفل (الوضع المشترك) // الرقم السلبي: يشير إلى أن الاستحواذ فشل // صفر القيمة: يشير إلى أن العقدة الحالية قد تم الحصول عليها بنجاح ، لكن العقدة الخفية لم تعد قادرة على الحصول على // رقم إيجابي: تشير إلى أن العقدة الحالية قد تم الحصول عليها بنجاح ، وأن العقدة الخفية يمكنها أيضًا الحصول على تجربتها (int remperation).إن استدعاء طريقة المكتسبة هو وسيلة لاكتساب القفل دون الاستجابة لمقاطعات الخيط. في هذه الطريقة ، يتم استدعاء TryAcquireshared أولاً لمحاولة الحصول على القفل. تقوم طريقة TryAcquireshared بإرجاع حالة الحصول على القفل. هنا يحدد AQS أنه إذا كانت حالة الإرجاع سلبية ، فهذا يعني أن العقدة الحالية تفشل في الحصول على القفل. إذا كانت 0 تعني أن العقدة الحالية تحصل على القفل ، ولكن لا يمكن الحصول على العقدة اللاحقة بعد الآن. إذا كان ذلك إيجابيًا ، فهذا يعني أن العقدة الحالية تحصل على القفل ، ويمكن أيضًا الحصول على العقد اللاحقة لهذا القفل بنجاح. عندما تنفذ الفئة الفرعية منطق الحصول على الأقفال بواسطة طريقة TryAcquireshared ، تحتاج قيمة الإرجاع إلى الامتثال لهذه الاتفاقية. إذا كانت قيمة إرجاع الاتصال TryAcquireshared أقل من 0 ، فهذا يعني أن محاولة الحصول على القفل فشلت. بعد ذلك ، اتصل بالطريقة doacquireshared لإضافة مؤشر الترابط الحالي إلى قائمة انتظار المزامنة. نرى طريقة doacquireshared.
// GET (الوضع المشترك) في قائمة انتظار المزامنة private void doacquireshared (int arg) {// أضف إلى قائمة قائمة انتظار المزامنة النهائية = addwaiter (node.shared) ؛ فشل Boolean = صحيح ؛ حاول {boolean interrupt = false ؛ لـ (؛؛) {// الحصول على العقدة الأمامية للعقدة النهائية الحالية p = node.predecessor () ؛ // إذا كانت العقدة الأمامية عبارة عن عقدة رأس ، فحاول الحصول على القفل مرة أخرى إذا (p == head) {// حاول الحصول على القفل مرة أخرى وإرجاع حالة الاستحواذ // r <0 ، مما يشير إلى أن الاستحواذ فشل // r = 0 ، مما يشير إلى أن العقل الحالي يتم الحصول عليه بنجاح ، ولكن لا يمكن الحصول على نود بعد ذلك/r تم الحصول عليها بنجاح int r = tryacquireshared (arg) ؛ إذا (r> = 0) {// إلى هذه النهاية ، فإنه يشير إلى أن العقدة الحالية قد اكتسبت القفل بنجاح. في هذا الوقت ، سيتم نشر معلومات حالة القفل إلى العقدة اللاحقة SetheadandPropagate (العقدة ، ص) ؛ p.next = null ؛ // إذا تم استلام طلب المقاطعة أثناء حظر مؤشرات الترابط ، فاستجاب للطلب في هذه الخطوة إذا (المقطع) {selfinterrupt () ؛ } فشل = خطأ ؛ يعود؛ }} // في كل مرة يفشل فيها اكتساب القفل ، سيحدد ما إذا كان يمكن تعليق الخيط. إذا كان ذلك ممكنًا ، فسيتم تعليق الخيط في طريقة parkandcheckinterrupt إذا (يجب أن (يجب أن يكون parkafterfailedacquire (p ، node) && parkandcheckinterrupt ()) {interrupted = true ؛ }}} أخيرًا {if (فشل) {cancelAcquire (node) ؛ }}}عند إدخال طريقة doacquireshared أولاً ، اتصل بطريقة AddWaiter لالتفاف الخيط الحالي في عقدة ووضعها في نهاية قائمة انتظار المزامنة. لقد تحدثنا عن عملية إضافة العقد عند الحديث عن الوضع الحصري ، لذلك لن أتحدث عنها هنا. بعد أن تدخل العقدة قائمة انتظار المزامنة ، إذا وجدت أن العقدة التي أمامها هي عقدة الرأس ، لأن مؤشر ترابط عقدة الرأس قد اكتسب القفل ودخل الغرفة ، فمن دوارها الحصول على القفل. لذلك ، لن تعلق العقدة الحالية نفسها أولاً ، ولكنها ستحاول الحصول على القفل مرة أخرى. إذا قام الشخص الموجود بإطلاق القفل والأوراق ، فيمكن للعقدة الحالية الحصول على القفل بنجاح. إذا لم يصدر الشخص الموجود القفل ، فسوف يتصل بالطريقة التي يجب على parkafterfailedacquire. في هذه الطريقة ، سيتم تغيير حالة عقدة الرأس للإشارة. فقط من خلال التأكد من أن حالة العقدة السابقة هي إشارة ، يمكن للعقدة الحالية أن تعلق نفسها بثقة. سيتم تعليق جميع المواضيع في طريقة ParkandCheckinterrupt. إذا حدث أن تحصل العقدة الحالية على القفل بنجاح ، فسيتم استدعاء طريقة SetheAdandPropagate لتعيين نفسها كعقدة الرأس وتستيقظ على العقدة التي يتم تقليصها أيضًا. دعنا نلقي نظرة على التشغيل المحدد لطريقة SetheAdandPropagate.
// قم بتعيين عقدة الرأس ونشر حالة القفل (الوضع المشترك) setheadandpropagate void private (Node Node ، int) {node h = head ؛ // قم بتعيين العقدة المعطاة مثل عقدة الرأس sethead (العقدة) ؛ // إذا كان الانتشار أكبر من 0 ، فهذا يعني أنه يمكن أن يحصل القفل إذا (الانتشار> 0 || h == null || h.waitstatus <0) {// الحصول على عقدة الخلف لعقدة العقدة المعطاة s = node.next ؛ // إذا كانت عقدة الخلف للعقدة المحددة فارغة ، أو أن حالتها هي حالة مشتركة إذا كانت (s == null || }}} // عملية القفل (الوضع المشترك) private void doreleaseshared () {for (؛؛) {// احصل على عقدة الرأس لعقدة قائمة الانتظار المتزامنة H = Head ؛ if (h! = null && h! = tail) {// احصل على حالة انتظار عقدة الرأس int ws = h.waitstatus ؛ // إذا كانت حالة عقدة الرأس هي إشارة ، فهذا يعني أن شخصًا ما يصطف وراء (ws == node.signal) {// احصل على حالة انتظار عقدة الرأس إلى 0 إذا (! compareAndSetWaitStatus (h ، node.signal ، 0)) {متابعة ؛ } // استيقظ على عقدة الخلف Unparksuccessor (H) ؛ // إذا كانت حالة عقدة الرأس هي 0 ، فهذا يعني أنه لا يوجد أحد في طوابير لاحقًا ، فقط قم بتعديل حالة الرأس لانتشار} آخر إذا (ws == 0 &&! compareandsetwaitstatus (h ، 0 ، node.propagate)) {متابعة ؛ }} // فقط عن طريق التأكد من عدم تعديل عقدة الرأس خلال الفترة ، يمكنك الخروج من الحلقة إذا (h == head) {break ؛ }}}إن استدعاء طريقة SetheAdandPropagate أولاً يعين نفسها على أنها عقدة الرأس ، ثم تقرر ما إذا كنت تريد إيقاظ عقدة الخلف بناءً على قيمة إرجاع طريقة TryAcquireshared التي تم تمريرها. كما ذكرنا سابقًا ، عندما تكون قيمة الإرجاع أكبر من 0 ، فهذا يعني أن العقدة الحالية قد اكتسبت القفل بنجاح ، ويمكن للعقدة اللاحقة أيضًا الحصول على القفل بنجاح. في هذا الوقت ، تحتاج العقدة الحالية إلى إيقاظ العقدة الموجودة أيضًا في الوضع المشترك. لاحظ أنه في كل مرة تستيقظ فيها ، لا تستيقظ فقط في العقدة التالية. إذا لم تكن العقدة الأخيرة في الوضع المشترك ، فستدخل العقدة الحالية الغرفة مباشرة ولن تستيقظ في العقدة الإضافية. يتم تنفيذ تشغيل العقد الخلف في الوضع المشترك في طريقة DoreleAseshared. عمليات الاستيقاظ للوضع المشترك والوضع الحصري هي نفسها في الأساس. يجد كلاهما العلامة التجارية على مقعدك (حالة الانتظار). إذا كانت العلامة التجارية إشارة ، فهذا يعني أن شخصًا ما يحتاج إلى المساعدة في إيقاظها لاحقًا. إذا كانت العلامة التجارية 0 ، فهذا يعني أنه لا يوجد أحد في قائمة الانتظار في قائمة الانتظار في هذا الوقت. في الوضع الحصري ، إذا وجدت أنه لا يوجد أحد في قائمة الانتظار ، فسوف تترك قائمة الانتظار مباشرة. في الوضع المشترك ، إذا وجدت أنه لا يوجد أحد في طابور خلف قائمة الانتظار ، فستظل العقدة الحالية تترك ملاحظة صغيرة قبل مغادرتها (تعيين حالة الانتظار للانتشار) لإخبار الأشخاص الذين يتوفرون في وقت لاحق عن الحالة المتاحة لهذا القفل. ثم عندما يمكن للشخص الذي يأتي لاحقًا الحكم على ما إذا كان سيحصل مباشرة على القفل بناءً على هذه الولاية.
2. الاستجابة لاكتساب المقاطعة
. } // 1. حاول الحصول على القفل if (tryacquireshared (arg) <0) {// 2. إذا فشل عملية الاستحواذ ، أدخل هذه الطريقة doacquiresharedInterrupturedruptreruptreruptreructible ؛ }} // الاستحواذ في الوضع القابل للمقاطعة (الوضع المشترك) private void doacquiresharedInterruptive (int arg) يلقي interruptedException {// أدخل العقدة الحالية في ذيل قائمة انتظار المزامنة النهائية node = addwaiter (node.shared) ؛ فشل Boolean = صحيح ؛ حاول {for (؛؛) {// الحصول على العقدة النهائية السابقة p = node.predecessor () ؛ if (p == head) {int r = tryacquireshared (arg) ؛ if (r> = 0) {setheAdandPropagate (node ، r) ؛ p.next = null ؛ فشل = خطأ ؛ يعود؛ }} if (shouldparkafterFailedAcquire (p ، node) && parkandcheckinterrupt ()) {// إذا استقبل مؤشر الترابط طلب المقاطعة أثناء عملية الحظر ، فسيقوم على الفور بإلقاء استثناء هنا }}} أخيرًا {if (فشل) {cancelAcquire (node) ؛ }}}إن طريقة الحصول على قفل استجابة لمقاطعات الخيط وطريقة الحصول على قفل استجابة لمقاطعات الخيط هي نفسها في هذه العملية. الفرق الوحيد هو المكان الذي يستجيب فيه لطلبات مقاطعة الموضوع. عندما لا تستجيب مقاطعة مؤشر الترابط لمقاطعة مؤشر الترابط لاكتساب القفل ، يتم إيقاظ الخيط من طريقة ParkandCheckinterrupt. بعد الاستيقاظ ، يعود على الفور ما إذا كان قد تم استلام طلب المقاطعة. حتى إذا تم استلام طلب المقاطعة ، فسوف يستمر في الدوران حتى يتم الحصول عليه حتى يستجيب لطلب المقاطعة ويعلق نفسه. سوف يستجيب الخيط على الفور لطلب المقاطعة بعد إيقاظ مؤشر الترابط. إذا تم استلام مقاطعة مؤشر الترابط أثناء عملية الحظر ، فسيتم إلقاء مقاطعات على الفور.
3. اضبط الوقت المهلة للحصول على
// الحصول على القفل مع مهلة محدودة (الوضع المشترك) النهائي النهائي tryacquiresharednanos (int arg ، nanostimeout الطويل) يلقي InterruptedException {if (thread.interrupted ()) } // 1. استدعاء TryAcquireshared لمحاولة الحصول على القفل // 2. إذا فشل الاستحواذ ، اتصل بـ doacquiresharednanos إرجاع TryAcquireshared (Arg)> = 0 || doacquiresharednanos (arg ، nanostimeout) ؛} // الحصول على القفل مع مهلة محدودة (الوضع المشترك) doacquiresharednans المنطقية الخاصة (int arg ، nanostimeout الطويل) يلقي interruptedException {long lastmime = system.nanotime () ؛ العقدة النهائية = AddWaiter (node.shared) ؛ فشل Boolean = صحيح ؛ حاول {for (؛؛) {// الحصول على العقدة السابقة للعقدة النهائية الحالية p = node.predecessor () ؛ if (p == head) {int r = tryacquireshared (arg) ؛ if (r> = 0) {setheAdandPropagate (node ، r) ؛ p.next = null ؛ فشل = خطأ ؛ العودة صحيح. }} // إذا تم استخدام المهلة ، فسيتم إنهاء عملية الاستحواذ وسيتم إرجاع معلومات الفشل إذا (nanostimeout <= 0) {return false ؛ } // 1. تحقق مما إذا كان يتم استيفاء متطلبات تعليق مؤشر الترابط (مضمون أن حالة العقدة الأمامية هي إشارة) // 2. تحقق مما إذا كان وقت المهلة أكبر من وقت الدوران إذا (يجب أن يكون (يجب أن يكون هناك parkafterfailedacquire (p ، node) && nanostimeout> sportfortimeouthold) {// إذا تم استيفاء الشرطين أعلاه ، فسيتم تعليق الخيط الحالي لفترة زمنية من زمنية locksuport.parknanos (هذا ، nanostimeout) ؛ } طويل الآن = system.nanotime () ؛ // وقت المهلة في كل مرة يقوم فيها بطرح وقت اكتساب القفل nanostimeout - = الآن - آخر مرة ؛ آخر مرة = الآن ؛ // إذا تم استلام طلب المقاطعة أثناء الحظر ، فسيتم طرح استثناء على الفور إذا (thread.interrupted ()) {رمي جديد interruptedException () ؛ }}} أخيرًا {if (فشل) {cancelAcquire (node) ؛ }}}إذا فهمت طريقتي الاستحواذ أعلاه ، فسيكون من السهل جدًا تعيين طريقة الاستحواذ لوقت المهلة. العملية الأساسية هي نفسها ، وخاصة فهم آلية المهلة. إذا تم الحصول على القفل لأول مرة ، فسيتم استدعاء طريقة doacquiresharednanos وسيتم تمرير وقت المهلة. بعد دخول الطريقة ، سيتم الحصول على القفل مرة أخرى وفقًا للموقف. إذا فشل القفل مرة أخرى ، فيجب اعتبار الخيط معلقًا. في هذا الوقت ، سنحدد ما إذا كان وقت المهلة أكبر من وقت الدوران. إذا كان الأمر كذلك ، فسيتم تعليق الخيط لفترة من الوقت. خلاف ذلك ، سوف نستمر في محاولة الحصول عليها. بعد كل مرة نحصل فيها على القفل ، سنطرح وقت القفل للحصول عليه. سنحلق مثل هذا حتى يتم استنفاد وقت المهلة. إذا لم يتم الحصول على القفل ، فسيتم إنهاء عملية الاستحواذ وسيتم إرجاع علامة فشل الاستحواذ. يستجيب الخيط لمقاطعات الخيط طوال الفترة.
4. عمليات إزالة العقد في الوضع المشترك
// تشغيل القفل (الوضع المشترك) النهائي النهائي المنطقي (int arg) {//1.try لإطلاق القفل if (tryreleaseshared (arg)) {// 2. إذا كان الإصدار ناجحًا ، استيقظ مؤشرات ترابط أخرى doreleaseshared () ؛ العودة صحيح. } return false ؛} // حاول إطلاق القفل (الوضع المشترك) المحمي Boolean Tryreleaseshared (int arg) {رمي جديد غير مدعوم غير مستقر () ؛} // تشغيل القفل (الوضع المشترك) void doreleaseshared () {for (؛ ؛) if (h! = null && h! = tail) {// احصل على حالة انتظار عقدة الرأس int ws = h.waitstatus ؛ // إذا كانت حالة عقدة الرأس هي إشارة ، فهذا يعني أن شخصًا ما يصطف لاحقًا إذا (ws == node.signal) {// أولاً قم بتحديث حالة انتظار عقدة الرأس إلى 0 إذا (! compareAndSetWaitStatus (h ، node.signal ، 0)) {متابعة ؛ } // استيقظ العقدة اللاحقة Unparksuccessor (H) ؛ // إذا كانت حالة عقدة الرأس 0 ، فهذا يعني أنه لا يوجد أحد في طوابير في وقت لاحق ، فإنه يغير حالة الرأس فقط إلى الانتشار} آخر إذا (ws == 0 &&! compareandsetwaitstatus (h ، 0 ، node.propagate)) {متابعة ؛ }} // لا يمكن كسر الحلقة إلا إذا (h == head) {break ؛ }}}بعد أن ينتهي الخيط في العمل في الغرفة ، سوف يدعو طريقة RELEASeshared لإطلاق القفل. أولاً ، سوف يطلق على طريقة Tryreleaseshared لمحاولة تحرير القفل. يتم تنفيذ منطق الحكم لهذه الطريقة بواسطة الفئة الفرعية. إذا نجح الإصدار ، اتصل بطريقة DoreleAseshared لإيقاظ عقدة الخلف. بعد الخروج من الغرفة ، سيجد المقعد الأصلي (عقدة الرأس) ومعرفة ما إذا كان أي شخص قد ترك ملاحظات صغيرة على المقعد (إشارة الحالة). إذا كان الأمر كذلك ، استيقظ عقدة الخلف. إذا لم يكن هناك (حالة 0) يعني أنه لا يوجد أحد في قائمة الانتظار في قائمة الانتظار ، فإن آخر ما يتعين عليه القيام به قبل المغادرة هو المغادرة ، وهو ترك ملاحظة صغيرة على مقعدها (تم تعيين الوضع للانتشار) لإخبار الأشخاص الذين يقفون وراء القفل للحصول على الدولة. الفرق الوحيد بين عملية إصدار القفل بأكملها والوضع الحصري هو العمل في هذه الخطوة الأخيرة.
ملاحظة: يعتمد جميع التحليلات المذكورة أعلاه على JDK1.7 ، وستكون هناك اختلافات بين الإصدارات المختلفة ، يحتاج القراء إلى الانتباه.
ما سبق هو كل محتوى هذه المقالة. آمل أن يكون ذلك مفيدًا لتعلم الجميع وآمل أن يدعم الجميع wulin.com أكثر.