لتعلم برمجة Concurrency Java ، علينا أن نتعرف على حزمة java.util.concurrent. هناك العديد من فئات أدوات التزامن التي نستخدمها في كثير من الأحيان ضمن هذه الحزمة ، مثل: reentrantlock ، countdownlatch ، cyclicbarrier ، semaphore ، إلخ. يعتمد التنفيذ الأساسي لهذه الفئات على فئة التجريدات المخصصة ، مما يدل على أهمية هذه الفئة. لذلك في سلسلة Concurrency Java ، قمت أولاً بتحليل فئة AbstractQueuedSynchronizer. نظرًا لأن هذه الفئة أكثر أهمية والرمز طويل نسبيًا ، من أجل تحليلها بشكل كامل قدر الإمكان ، قررت استخدام أربع مقالات لإعطاء مقدمة كاملة نسبيًا لهذه الفئة. هذه المقالة هي مقدمة ملخص لمنح القراء فهمًا أوليًا لهذه الفئة. من أجل بساطة السرد ، ستستخدم بعض الأماكن AQS لتمثيل هذه الفئة في المستقبل.
1. ما هي فئة التجريدة extructedsynchronizer؟
أعتقد أن العديد من القراء قد استخدموا reentrantlock ، لكنهم لا يعرفون وجود التجريدي. في الواقع ، يقوم REENTRANTLOCK بتنفيذ مزامنة من الطبقة الداخلية ، والتي ترث التجريدية. تعتمد جميع تطبيقات آلية القفل على الفصول الداخلية المزامنة. يمكن القول أيضًا أن تنفيذ reentrantlock يعتمد على فئة التجريدية. وبالمثل ، فإن فئات CountDownlatch و Cyclicbarrier و Smaphore تستخدم أيضًا نفس الطريقة لتنفيذ التحكم الخاص بها في الأقفال. يمكن أن نرى أن التجريبي QualuedSynchronizer هو حجر الزاوية في هذه الفئات. إذن ما الذي يتم تنفيذه بالضبط داخل AQS بحيث تعتمد كل هذه الفئات عليها؟ يمكن القول أن AQS توفر البنية التحتية لهذه الفئات ، أي أنها توفر قفل كلمة المرور. بعد أن تحتوي هذه الفئات على قفل كلمة مرور ، يمكنهم تعيين كلمة مرور قفل كلمة المرور بأنفسهم. بالإضافة إلى ذلك ، توفر AQS أيضًا منطقة قائمة انتظار ومدرب مؤشر ترابط. نحن نعلم أن الخيوط مثل البربرية البدائية. إنهم لا يعرفون كيف يكونون مهذبين. سوف يندفعون فقط ، لذلك عليك أن تعلّمها خطوة بخطوة ، وتخبرها عندما تحتاج إلى قائمة الانتظار ، وأين يمكنك الانتظار ، وماذا تفعل قبل الانتظار ، وماذا تفعل بعد الانتظار. يتم الانتهاء من كل هذه الأعمال التعليمية من قبل AQS لك. أصبحت المواضيع المتعلمة منها متحضرة ومهذبة للغاية ، ولم تعد البرابرة البدائية. لذلك في المستقبل ، نحتاج فقط إلى التعامل مع هذه الخيوط المتحضرة. لم يكن لديك الكثير من الاتصال مع المواضيع الأصلية!
2. لماذا يوفر AbstractQueuedSynchronizer قفل كلمة المرور؟
// عقدة الرأس لقائمة انتظار المزامنة رأس العقدة المتطايرة العابرة ؛ . {return unsafe.compareanswapint (هذا ، stateoffset ، توقع ، تحديث) ؛}يسرد الرمز أعلاه جميع متغيرات الأعضاء في AQS. يمكنك أن ترى أن هناك ثلاثة متغيرات فقط من أعضاء AQS ، وهي مرجع عقدة رأس قائمة انتظار المزامنة ، ومرجع عقدة ذيل قائمة انتظار المزامنة ، وحالة التزامن. لاحظ أنه يتم تعديل جميع المتغيرات الثلاثة للأعضاء مع الكلمة الرئيسية المتطايرة ، مما يضمن تعديل مؤشرات الترابط المتعددة أنها مرئية للذاكرة. جوهر الفصل بأكمله هو حالة التزامن هذه. يمكنك أن ترى أن حالة التزامن هي في الواقع متغير من النوع الداخلي. يمكنك اعتبار حالة التزامن هذه قفل كلمة مرور ، وهي أيضًا قفل كلمة مرور مغلقة من الغرفة. القيمة المحددة للحالة تعادل كلمة المرور التي تتحكم في فتح وإغلاق قفل كلمة المرور. بالطبع ، يتم تحديد كلمة مرور هذا القفل بواسطة كل فئة فرعية. على سبيل المثال ، في Reentrantlock ، تعني الحالة المتساوية 0 أن القفل مفتوح ، والدولة أكبر من 0 تعني أن القفل مغلق ، وفي إشارة ، تعني الحالة أكبر من 0 أن القفل مفتوح ، والدولة تساوي 0 تعني أن القفل مغلق.
3. كيف يتم تنفيذ منطقة قائمة الانتظار لـ AbstractQueuedSynchronizer؟
يوجد في الواقع منطقتان في قائمة الانتظار داخل AbstractQueuedSynchronizer ، أحدهما قائمة انتظار متزامنة والآخر قائمة انتظار مشروطة. كما يتضح من الشكل أعلاه ، لا يوجد سوى قائمة انتظار مزامنة واحدة ، بينما يمكن أن يكون هناك قوائم انتظار متعددة. عقدت العقد في قائمة الانتظار المتزامنة إشارات إلى العقد الأمامية والخلفية على التوالي ، في حين أن العقد في قائمة الانتظار الشرطية لديها إشارة واحدة فقط إلى عقدة الخلف. في الشكل ، يمثل T موضوع. كل عقدة تحتوي على موضوع. بعد فشل الخيط في الحصول على القفل ، يدخل أولاً قائمة انتظار المزامنة إلى قائمة الانتظار. إذا كنت ترغب في إدخال قائمة الانتظار الشرطية ، فيجب أن يحمل مؤشر الترابط القفل. بعد ذلك ، دعنا نلقي نظرة على بنية كل عقدة في قائمة الانتظار.
. // يحمل مؤشر الترابط الحالي القفل في الوضع المشترك Static Final Node Exclusive = NULL ؛ // يحمل مؤشر الترابط الحالي القفل في الوضع الحصري الثابت النهائي الذي تم إلغاؤه = 1 ؛ // ألغت العقدة الحالية إشارة القفل الثابتة int = -1 ؛ // تحتاج مؤشرات ترابط العقدة الخلف إلى تشغيل حالة int نهائية ثابتة = -2 ؛ // العقدة الحالية في قائمة الانتظار في قائمة الانتظار الشرطية الثابتة int الانتشار = -3 ؛ // يمكن للعقدة اللاحقة الحصول مباشرة على القفل المتقلبة int waitstatus ؛ // تشير إلى حالة انتظار العقدة المتطايرة الحالية. // تشير إلى العقدة الأمامية في عقدة قائمة انتظار المزامنة المقبلة ؛ // تشير إلى عقدة الخلف في مؤشر ترابط مؤشر ترابط قوائم قائمة انتظار المزامنة ؛ // يشير مؤشر الترابط الذي تحتفظ به العقدة الحالية إلى Node NextWaiter ؛ // تشير إلى عقدة الخلف في قائمة الانتظار الشرطية // هي حالة العقدة الحالية في الوضع المشترك النهائي iSshared () {return nextWaiter == shared ؛ } // إرجاع العقدة الأمامية لسلف العقدة النهائية الحالية () سلف NullPointerException {node p = prev ؛ if (p == null) {رمي nullpointerxception () جديدة ؛ } آخر {return p ؛ }} // constructor 1 node () {} // constructor 2 ، يتم استخدام هذا المُنشئ عن طريق العقدة الافتراضية (مؤشر ترابط ، وضع العقدة) {// لاحظ أن وضع الحجز يتم تعيينه إلى nextWaiter this.nextWaiter = mode ؛ this.Thread = thread ؛ } // constructor 3 ، يتم استخدام العقدة فقط (مؤشر ترابط ، int waitstatus) في قائمة انتظار الشرط {this.waitstatus = waitstatus ؛ this.Thread = thread ؛ }}تمثل العقدة عقدة في قائمة انتظار المزامنة وقائمة الانتظار الشرطي. إنها فئة داخلية من الملخص. تحتوي العقدة على العديد من السمات ، مثل وضع الانتظار ، وحالة الانتظار ، والتسلسل المسبق والخليفة في قوائم الانتظار المتزامنة ، ومراجع الخلف في قوائم الانتظار الشرطية ، وما إلى ذلك. يمكن اعتبار قائمة انتظار المزامنة وقائمة انتظار الحالة كضيف طابور. عندما يأتي الضيوف لأول مرة ، سوف يطرقون الباب لمعرفة ما إذا تم فتح القفل. إذا لم يتم فتح القفل ، فسوف يذهبون إلى منطقة قائمة الانتظار لجمع لوحة أرقام ، ويعلنون الطريقة التي يريدون بها الاحتفاظ بالقفل ، وأخيراً في نهاية قائمة الانتظار.
4 كيف فهم الوضع الحصري ووضع المشاركة؟
كما ذكرنا سابقًا ، سيتلقى كل ضيف لوحة أرقام قبل الانتظار ويعلن أنه يريد امتلاك القفل. تنقسم طريقة امتلاك القفل إلى وضع حصري ووضع المشاركة. إذن كيف تفهم الوضع الحصري ووضع المشاركة؟ أنا حقا لا أستطيع أن أجد أي تشبيه جيد. يمكنك التفكير في مرحاض عام. الأشخاص الذين يعانون من الوضع الحصري هم أكثر ندوة. أنا إما لا أذهب. عندما أتيت ، لا أسمح للآخرين بالدخول. أشغل المرحاض بأكمله وحده. الأشخاص في وضع المشاركة ليسوا خاصين. عندما يجدون أن المرحاض قابل للاستخدام بالفعل ، فإنه لا يتم احتسابه إذا كان ذلك بمفرده. عليهم أيضًا أن يسألوا بحماس الأشخاص وراءهم ما إذا كانوا يمانعون في استخدامه معًا. إذا كان الأشخاص الذين يقفون وراءهم لا يمانعون في استخدامه معًا ، فليست هناك حاجة إلى الانتظار. الجميع سوف يذهب معا. بالطبع ، إذا تمانع الأشخاص الذين يقفون وراءهم ، فيجب عليهم البقاء في قائمة الانتظار والاستمرار في قائمة الانتظار.
5 كيف تفهم حالة انتظار العقدة؟
نرى أيضًا أن كل عقدة لديها حالة انتظار ، تنقسم إلى أربع ولايات: إلغاء ، الإشارة ، الحالة ، والانتشار. يمكن اعتبار حالة الانتظار هذه علامة معلقة بجوار المقعد ، وتحديد حالة انتظار الشخص على المقعد الحالي. لا يمكن تعديل حالة هذه العلامة التجارية فقط ، ولكن يمكن للآخرين أيضًا تعديلها. على سبيل المثال ، عندما يكون هذا الموضوع يخطط بالفعل للتخلي أثناء قائمة الانتظار ، فإنه سيضع العلامة على مقعده على إلغاؤه ، حتى يتمكن الآخرون من مسحه من قائمة الانتظار إذا رأوا ذلك. هناك موقف آخر هو أنه عندما يكون الخيط على وشك النوم في المقعد ، فهذا يخشى أن يفرط في النوم ، لذلك سيغير العلامة في الوضع الأمامي للإشارة ، لأن الجميع سيعود إلى مقاعدهم قبل مغادرة قائمة الانتظار لإلقاء نظرة. إذا رأى أن الحالة على الإشارة هي إشارة ، فسوف تستيقظ على الشخص التالي. فقط من خلال ضمان أن العلامة التجارية في الوضع الأمامي هي إشارة ، سينام الخيط الحالي بسلام. تشير حالة الحالة إلى أن الخيط في قائمة الانتظار في قائمة الانتظار الشرطية. تذكر حالة الانتشار الخيوط اللاحقة للحصول على القفل مباشرة. يتم استخدام هذه الحالة فقط في الوضع المشترك ، وسيتم مناقشتها لاحقًا عند الحديث عن الوضع المشترك بشكل منفصل.
6. ما هي العمليات التي سيتم تنفيذها عندما تدخل العقدة في قائمة انتظار المزامنة؟
// Node Enqueue Operation ، ارجع إلى العقدة الخاصة العقدة السابقة ENQ (عقدة العقدة النهائية) {for (؛؛) {// الحصول على المرجع إلى عقدة الذيل لعقدة قائمة انتظار التزامن t = tail ؛ // إذا كانت عقدة الذيل فارغة ، فهذا يعني أنه لم يتم تهيئة قائمة انتظار المزامنة إذا (t == null) {// تهيئة قائمة انتظار المزامنة إذا (المقارنات (node ())) {tail = head ؛ }} آخر {// 1. أشر إلى عقدة الذيل الحالية. // 2. اضبط العقدة الحالية على عقدة الذيل if (CompareAndSetTail (t ، node)) {// 3. أشر على خليفة عقدة الذيل القديمة إلى عقدة الذيل الجديدة T.Next = Node ؛ // المخرج الوحيد للحلقة من أجل إرجاع t ؛ }}}}لاحظ أن عملية enqueue تستخدم حلقة ميتة. فقط عندما تتم إضافة العقدة بنجاح إلى ذيل قائمة انتظار المزامنة ، سيتم إرجاعها. والنتيجة هي عقدة الذيل الأصلية لقائمة انتظار التزامن. يوضح الشكل التالي عملية التشغيل بأكملها.
يحتاج القراء إلى الانتباه إلى ترتيب إضافة عقد الذيل ، والتي يتم تقسيمها إلى ثلاث خطوات: الإشارة إلى العقد الذيل ، وتغيير CAS العقد الذيل ، وإشارة خلفاء العقدة التيل القديمة إلى العقدة الحالية. في بيئة متزامنة ، قد لا يتم إكمال هذه الخطوات الثلاث. لذلك ، في تشغيل مسح جميع العقد الملغاة في قائمة انتظار المزامنة ، من أجل العثور على العقد في حالة غير مشرقة ، لا يتم اجتيازها من الأمام إلى الخلف ولكن من الخلف إلى الأمام. أيضًا ، عندما تدخل كل عقدة في قائمة الانتظار ، تكون حالة الانتظار 0.
ملاحظة: يعتمد جميع التحليلات المذكورة أعلاه على JDK1.7 ، وستكون هناك اختلافات بين الإصدارات المختلفة ، يحتاج القراء إلى الانتباه.
ما سبق هو كل محتوى هذه المقالة. آمل أن يكون ذلك مفيدًا لتعلم الجميع وآمل أن يدعم الجميع wulin.com أكثر.