تحليل رمز المصدر العددي - العد التنازلي ()
تحدثت المقالة السابقة عن مبدأ AWAIT () في CountDownlatch من مستوى رمز المصدر. يتحدث هذا المقال عن العد التنازلي ().
العد التنازلي public void () {// countDownlatch sync.releaseshared (1) ؛} ↓ Releaseshared النهائي العام (int arg) {// aqs if (tryreleaseshared (arg)) {doreleaseshared () ؛ العودة صحيح. } return false ؛} ↓ محمية tryreleaseshared (int reease) {//countdownlatch.sync // compl count ؛ إشارة عند الانتقال إلى الصفر لـ (؛؛) {int c = getState () ؛ إذا (C == 0) العودة خطأ ؛ int nextc = c-1 ؛ if (compareAndSetState (c ، nextc)) return nextc == 0 ؛ }}من خلال End CountDownlatch End = New CountDownLatch (2) ؛ تم تعيين الحالة على 2 ، لذلك C == 2 ، NextC = 2-1 ،
ثم اضبط الحالة على 1 من خلال عملية CAS التالية.
محمي نهائي المقارنات Boolean CompareAndSetState (int توقع ، int) {// انظر أدناه لدعم الإعداد الداخلي لدعم هذه العائد unfafe. }في هذا الوقت ، NextC ليس 0 ، ويعيد خطأ. انتظر حتى تسمى طريقة العد التنازلي () مرتين ، الحالة == 0 ، nextc == 0 ، ويعود صحيح في هذا الوقت.
أدخل طريقة DoreleAseshared ().
DoreleAseshared () ؛ private private void doreleaseshared () { / * * تأكد من انتشار الإصدار ، حتى لو كان هناك نسخ /إصدارات أخرى. يستمر هذا في طريقة * المعتادة لمحاولة unparksuccors من الرأس إذا كانت بحاجة إلى إشارة *. ولكن إذا لم يحدث ذلك ، فسيتم تعيين الحالة على الانتشار للتأكد من أنه عند الإفراج ، يستمر الانتشار. * بالإضافة إلى ذلك ، يجب أن نحلق في حالة إضافة عقدة جديدة * أثناء قيامنا بذلك. أيضًا ، على عكس الاستخدامات الأخرى لـ * unparksuccessor ، نحتاج إلى معرفة ما إذا كان CAS لإعادة تعيين حالة * فشل ، إذا كان ذلك يعيد فحصه. */ for (؛؛) {node h = head ؛ if (h! = null && h! = tail) {int ws = h.waitstatus ؛ if (ws == node.signal) {if (! compareAndSetWaitStatus (h ، node.signal ، 0)) متابعة ؛ // حلقة لإعادة فحص الحالات Unparksuccessor (H) ؛ } آخر إذا (ws == 0 &&! compareAndSetWaitStatus (h ، 0 ، node.propagate)) متابعة ؛ // loop on fans cas} if (h == head) // loop إذا تغير الرأس ؛ }}تذكر نموذج قائمة انتظار الانتظار في هذا الوقت.
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
في هذا الوقت ، الرأس ليس فارغًا أو ذيلًا. WaitStatus == node.signal ، لذا أدخل الحكم إذا (! compareAndSetWaitStatus (h ، node.signal ، 0)).
if (! compareandsetwaitstatus (h ، node.signal ، 0)) ↓ /*** cas waitstatus حقل العقدة. */private Static Final Boolean CompareAndSetWaitStatus (عقدة العقدة ، int توقع ، int update) {return unsafe.compareanswapint (Node ، WaitStatusoff ، توقع ، تحديث) ؛}تقوم عملية CAS هذه بتعيين الحالة إلى 0 ، مما يعني أن Waitstatus في الرأس هو 0 في هذا الوقت. نموذج قائمة الانتظار كما يلي
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
هذه الطريقة ترجع صحيح. أدخل Unparksuccessor (H) ؛
Unparksuccessor (H) ؛ unpered private void unparksuccessor (عقدة العقدة) { / * * إذا كانت الحالة سالبة (أي ، إشارة محتملة) حاول * للتحقق من الإشارة. لا بأس إذا فشل هذا * أو إذا تم تغيير الحالة بواسطة مؤشر ترابط الانتظار. */ int ws = node.waitstatus ؛ إذا (ws <0) CompareAndSetWaitStatus (العقدة ، WS ، 0) ؛ / * * * يتم عقد مؤشر ترابط إلى unpark في الخلف ، وهو عادة * فقط العقدة التالية. ولكن إذا تم إلغاؤه أو على ما يبدو لاغية ، فالتوافس للخلف من الذيل للعثور على الخلف الفعلي * غير الملاحق. */ node s = node.next ؛ if (s == null || s.waitstatus> 0) {s = null ؛ لـ (node t = tail ؛ t! = null && t! = node ؛ t = t.prev) if (t.waitstatus <= 0) s = t ؛ } إذا (s! = null) locksupport.unpark (s.Thread) ؛}S هي عقدة الخلف للرأس ، أي العقدة مع الخيط الحالي. s! = null ، و s.waitstatus == 0 ، لذلك أدخل locksupport.unpark (s.Thread) ؛
public static void unpark (thread thread) {if (thread! = null) unfafe.unpark (thread) ؛ }وهذا هو ، الخيط الذي يفتح المحظور. سمح للحكم بتفجير صافرة!
مبدأ العد التنازلي () واضح للغاية.
في كل مرة يتم فيها تنفيذ طريقة العد التنازلي () ، يتم تقليل الحالة بمقدار 1. حتى الحالة == 0 ، تبدأ المواضيع المحصورة في قائمة الانتظار ، ويتم إصدار المواضيع في العقد اللاحقة وفقًا لحالة WaitStatus في العقدة السابقة.
حسنًا ، عد إلى مسألة المقالة السابقة ، متى ستندلع الحلقة التالية (الحلقة في طريقة الانتظار)
لـ (؛؛) {Final Node p = node.predecessor () ؛ if (p == head) {int r = tryacquireshared (arg) ؛ if (r> = 0) {setheAdandPropagate (node ، r) ؛ p.next = null ؛ // فشل مساعدة GC = false ؛ يعود؛ }} if (shouldparkafterfailedacquire (p ، node) && parkandcheckinterrupt ()) رمي interruptededexception () ؛}في هذا الوقت ، الحالة == 0 ، لذا أدخل طريقة SetheAdandPropagate.
SetheAdandPropagate (Node ، R) ؛ ↓ private void setheadandPropagate (عقدة العقدة ، int الانتشار) {node h = head ؛ // سجل الرأس القديم للتحقق أدناه sethead (العقدة) ؛ / *. الاستيقاظ غير الضروري ، ولكن فقط عندما يكون هناك العديد من الإصدارات/الإصدارات ، لذلك يحتاج معظمهم إلى إشارات الآن أو قريبًا * على أي حال. */ if (propergate> 0 || h == null || h.waitstatus <0 || (h = head) == null || h.waitstatus <0) {node s = node.next ؛ if (s == null || s.isshared ()) doreleaseshared () ؛ }} ↓ private void sethead (node node) {head = node ؛ Node.Thread = null ؛ node.prev = null ؛}هذه الطريقة تغير عقدة الخلف من الرأس إلى الرأس. بعد هذه الطريقة ، يتم ضبط العقدة التالية للعقدة على NULL ، ويصبح النموذج هو الشكل التالي
السابق +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
وهذا هو ، يتم ضبط ذيل رأس العقدة وأشياء أخرى على NULL ، في انتظار إعادة تدوير GC. في هذا الوقت ، العودة ، القفز من الحلقة ، ويتم مسح قائمة الانتظار.
هنا مظاهرة للعملية برمتها
SetheAdandPropagate (Node ، R) ؛ +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- الموضوع = فارغ | <---- العقدة (الذيل) | CurrentThread | +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- العقدة (ذيل) | CurrentThread | + -------------------------------+ ↓ +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
جوهر CountDownlatch هو قائمة انتظار مؤشر ترابط الحظر ، وهي قائمة انتظار تم إنشاؤها من قائمة مرتبطة ، والتي تحتوي على مؤشر ترابط و Waitstatus ، حيث تصف WaitStatus حالة مؤشر ترابط العقدة الخلف.
الدولة هي علم مهم جدا. عند البناء ، يتم تعيينه على القيمة n المقابلة. إذا كان n! = 0 ، سيتم حظر قائمة انتظار الحظر طوال الوقت ما لم يتم مقاطعة الخيط.
في كل مرة يتم فيها استدعاء طريقة العد التنازلي () ، يتم استخدام الحالة -1 ، ويتم استخدام طريقة AWAIT () لإضافة مؤشر الترابط الذي يدعو الطريقة إلى قائمة انتظار الحظر حتى الحالة == 0 ، ولا يمكن إصدار مؤشر الترابط.
ما سبق هو كل محتوى هذه المقالة. آمل أن يكون ذلك مفيدًا لتعلم الجميع وآمل أن يدعم الجميع wulin.com أكثر.