في الحياة الحقيقية ، نواجه مثل هذا الموقف. قبل أن ننفذ نشاطًا ، نحتاج إلى الانتظار حتى يكون لدينا جميع الناس قبل أن نبدأ. على سبيل المثال ، عند تناول الطعام ، يجب أن تنتظر حتى تكون العائلة بأكملها على المقعد قبل المضي قدمًا ، وعند السفر ، يجب عليك الانتظار حتى ينطلق الجميع قبل الانطلاق ، وعندما يكون الرياضيون في الملعب ، يجب عليك الانتظار حتى يصبح الرياضيون في الملعب قبل أن تبدأ. توفر لنا حزمة JUC فئة أداة التزامن يمكنها محاكاة هذا النوع من السيناريوهات بشكل جيد ، وهي فئة Cyclicbarrier. باستخدام فئة CyclicBarrier ، يمكن تنفيذ مجموعة من المواضيع لانتظار بعضها البعض ، ثم تنفيذ العمليات اللاحقة عندما تصل جميع مؤشرات الترابط إلى نقطة حاجز معينة. يوضح الشكل التالي هذه العملية.
هناك عداد داخل فئة Cyclicbarrier. سيتصل كل مؤشر ترابط بطريقة الانتظار لمنع نفسه عندما يصل إلى نقطة الحاجز. في هذا الوقت ، سيتم تقليل العداد بمقدار 1. عندما يتم تناقص العداد إلى 0 ، سيتم إيقاظ جميع الخيوط التي تم حظرها عن طريق استدعاء طريقة الانتظار. هذا هو مبدأ تنفيذ مجموعة من المواضيع التي تنتظر بعضها البعض. دعونا أولاً نلقي نظرة على ما لدى متغيرات الأعضاء Cyclicbarrier.
// قفل التشغيل المتزامن الخاص قفل نهائي خاص REENTRANTLOCK = جديد reentrantlock () ؛ // اعتراض الخيط رحلة الشرط النهائي الخاص = lock.newcondition () ؛ // عدد المواضيع المعتادة في كل مرة من الأطراف النهائية الخاصة بالجيل () جيل فئة ثابتة جيل {boolean broken = false ؛}يتم نشر جميع متغيرات الأعضاء في Cyclicbarrier أعلاه. يمكنك أن ترى أن Cyclicbarrier الداخلي يمنع الخيوط من خلال رحلة قائمة انتظار مشروطة ، ويحافظ على طرفين من نوع int-type والعد. تمثل الأطراف عدد مؤشرات الترابط التي تم اعتراضها في كل مرة ، ويتم تعيين هذه القيمة أثناء البناء. العداد هو عداد داخلي ، وقيمته الأولية هي نفسها مثل الأطراف ، ثم تنخفض بمقدار 1 مع كل مكالمة من طريقة الانتظار حتى يتم تقليلها إلى 0 وتستيقظ على جميع الخيوط. يمتلك Cyclicbarrier توليد فئة داخلي ثابت ، وتمثل كائنات هذه الفئة الجيل الحالي من السياج ، تمامًا مثل اللعبة التي تمثلها اللعبة عند لعب اللعبة ، باستخدامها لإدراك حلقة الانتظار. يشير BarrierCommand إلى المهمة التي تم تنفيذها قبل الاستبدال. عندما يتم تخفيض العد إلى 0 ، فهذا يعني أن اللعبة قد انتهت ويجب نقلها إلى اللعبة التالية. سيتم إيقاظ جميع خيوط الحظر قبل الذهاب إلى اللعبة التالية. قبل استيقاظ جميع المواضيع ، يمكنك تنفيذ المهام الخاصة بك عن طريق تحديد BarrierCommand. بعد ذلك نلقي نظرة على مُنشئه.
// constructor 1public Cyclicbarrier (int pliss ، barrieraction) {if (الأطراف <= 0) رمي غير alualtalargumentException () ؛ this.parties = الأطراف ؛ this.count = الأطراف ؛ this.barrierCommand = BarrierAction ؛} // Constructor 2public Cyclicbarrier (int pliss) {this (الأطراف ، null) ؛}يحتوي Cyclicbarrier على مُنشئين ، حيث يكون المنشئ 1 مُنشئه الأساسي. يمكنك هنا تحديد عدد المشاركين في هذه اللعبة (عدد المواضيع التي سيتم اعتراضها) والمهام التي سيتم تنفيذها في نهاية هذه اللعبة. يمكنك أيضًا أن ترى أن القيمة الأولية لعدد العداد يتم تعيينها على الأطراف. تتمثل الوظيفة الرئيسية لفئة CyclicBarrier في منع الخيط الذي يصل إلى نقطة الحاجز أولاً وانتظر الخيط اللاحق. إنه يوفر طريقتين للانتظار ، وهما الانتظار الموقوتون والانتظار غير التوقيت.
. } catch (timeOutException toe) {رمي خطأ جديد (إصبع القدم) ؛ }} // انتظار الانتظار العام int في انتظار (antyout ، وحدة الوقت) remrows interruptedException ، brokenBarrieRexception ، timeoutexception {return dowait (true ، unt.tonanos (timeout)) ؛}يمكن أن نرى أنه سواء كان انتظارًا أو غير محدد ، فإنهم يطلقون على طريقة dowait ، لكن المعلمات التي تم تمريرها مختلفة. دعونا نلقي نظرة على ما تفعله طريقة dowait.
// طريقة انتظار Core الخاصة int dowait (Boolean Timed ، nanos الطويل) يلقي InterruptedException ، brokenBarrieRexception ، timeoutexception {final reentrantlock lock = this.lock ؛ lock.lock () ؛ حاول {الجيل النهائي g = الجيل ؛ // تحقق من ما إذا كان السياج الحالي قد تم إخراجه إذا (g.broken) {رمي جديد BrokenBarriException () ؛ } // تحقق مما إذا كان مؤشر الترابط الحالي قد تم مقاطعة if (thread.interrupted ()) {// إذا تم مقاطعة مؤشر الترابط الحالي ، فسيتم القيام بالأشياء الثلاثة التالية // 1. تفجير السياج الحالي // 2. استيقظ جميع المواضيع المعتادة // 3. رمي مقاطعة استثناء breakbarrier () ؛ رمي جديد interruptedException () ؛ } // في كل مرة ، تقليل قيمة العداد بمؤشر int 1 = -count ؛ // قلل قيمة العداد إلى 0 ، تحتاج جميع مؤشرات الترابط إلى الاستيقاظ والتحويل إلى الجيل التالي إذا (index == 0) {boolean runAction = false ؛ حاول {// تنفيذ المهمة المحددة قبل استيقاظ جميع المواضيع النهائية Runnable Command = BarrierCommand ؛ if (command! = null) {command.run () ؛ } runAction = true ؛ // استيقظ جميع المواضيع وانتقل إلى الجيل التالي nextGeneration () ؛ العودة 0 ؛ } أخيرًا {// تأكد من إيقاظ جميع مؤشرات الترابط إذا (! runAction) {breakBarrier () ؛ }}} // إذا لم يكن العداد 0 ، فقم بتنفيذ هذه الحلقة لـ (؛؛) {try {// الدفاع لتحديد ما إذا كان يجب الانتظار في توقيت أم لا يتم توقيته إذا (! timed) {trip.await () ؛ } آخر إذا (nanos> 0l) {nanos = trip.awaitnanos (nanos) ؛ }} catch (interruptedException ie) {// إذا تم انقطاع الخيط الحالي أثناء الانتظار ، فقم بإلقاء السياج لإيقاظ مؤشرات ترابط أخرى إذا (g == generation &&! g.broken) {breakBarrier () ؛ رمي أي ؛ } آخر {// إذا تم الانتهاء من الانتظار على السياج قبل التقاط استثناء المقاطعة ، فإن عملية المقاطعة تسمى مباشرة thread.currentThread (). interrupt () ؛ }} // إذا تم إيقاظ مؤشر الترابط بسبب عملية الانقلاب ، يتم إلقاء استثناء إذا (g.broken) {رمي New BrokenBarriException () ؛ } // إذا تم إيقاف مؤشر الترابط بسبب عملية الاستبدال ، فإنه يعيد قيمة العداد إذا (g! = generation) {return index ؛ } // إذا تم إيقاظ الموضوع بسبب الوقت ، فإنه يقرع السياج ويرمي الاستثناء إذا (timed && nanos <= 0l) {breakBarrier () ؛ رمي timeOtexception () ؛ }}} أخيرًا {lock.unlock () ؛ }}التعليقات الواردة في الكود المذكور أعلاه مفصلة تمامًا ، لذلك سنختار فقط التعليقات المهمة. يمكنك أن ترى أن العد يتم تقليله بمقدار 1 في كل مرة في طريقة dowait. بعد الانخفاض ، يتم تحديده على الفور لمعرفة ما إذا كان يساوي 0. إذا كان يساوي 0 ، فسيتم تنفيذ المهمة المحددة من قبل. بعد التنفيذ ، سوف يدعو طريقة NextGeneration لنقل السياج إلى الجيل القادم. في هذه الطريقة ، سيتم إيقاظ جميع المواضيع ، وسيتم إعادة تعيين قيمة العداد إلى الأطراف ، وسيتم إعادة تعيين توليد السياج. بعد تنفيذ طريقة NextGeneration ، فهذا يعني أن اللعبة ستدخل اللعبة التالية. إذا كان العداد لا يساوي 0 في هذا الوقت ، فسيتم إدخال الحلقة. قرر ما إذا كان يجب استدعاء Trip.awaitnanos (Nanos) أو Trip.await () أساليب بناءً على المعلمات. تتوافق هاتان الطريقتان مع التوقيت والانتظار غير المحدد. إذا تمت مقاطعة الخيط الحالي أثناء الانتظار ، فسيتم تنفيذ طريقة BreakBarrier. تسمى هذه الطريقة كسر السياج ، مما يعني أن اللعبة مقطوعة في منتصف الطريق ، تضع حالة التوليد المكسورة على جميع الخيوط. في الوقت نفسه ، هذا يعني أيضًا أنه خلال عملية الانتظار ، يتم مقاطعة الخيط وينتهي اللعبة بأكملها ، وسيتم إيقاظ جميع الخيوط المحظورة مسبقًا. بعد أن يستيقظ الخيط ، سيؤدي الأحكام الثلاثة التالية لمعرفة ما إذا كان قد استيقظ بسبب استدعاء طريقة BreakBarrier. إذا كان الأمر كذلك ، فسوف يلقي استثناء ؛ معرفة ما إذا كان قد استيقظ من خلال عملية استبدال عادية. إذا كان الأمر كذلك ، فسيؤدي إلى إرجاع قيمة العداد ؛ معرفة ما إذا كان قد استيقظ بسبب المهلة. إذا كان الأمر كذلك ، فسوف يدعو BreakBarer لكسر السياج ورمي استثناء. تجدر الإشارة هنا أيضًا إلى أنه إذا خرج أحد المواضيع لأنها تنتظر المهلة ، فستكون اللعبة بأكملها ستنتهي وسيتم إيقاظ المواضيع الأخرى. تنشر ما يلي الرموز المحددة لطريقة NextGeneration وطريقة BreakBarrier.
// Switch Fence to the Generation private void nextGeneration () {// استيقظ جميع المواضيع في رحلة قائمة الانتظار. // قم بتعيين قيمة العداد على عدد مؤشرات الترابط التي يجب اعتراضها = الأطراف ؛ // إعادة تعيين جيل السياج = الجيل الجديد () ؛} // اقلب السياج الحالي الفراغ الخاص () {// اضبط حالة السياج الحالي على تقلب الجيل. broken = true ؛ // قم بتعيين قيمة العداد على عدد مؤشرات الترابط التي يجب اعتراضها = الأطراف ؛ // استيقظ جميع المواضيع Trips.Signalall () ؛}لقد شرحنا أساسا مبادئ Cyclicbarrier من خلال رمز المصدر أعلاه. دعنا نتعلم المزيد عن استخدامه من خلال مثال سباق الخيل.
Class Horse تنفذ Runnable {private static int counter = 0 ؛ معرف int النهائي الخاص = counter ++ ؛ خطوات خاصة int = 0 ؛ راند العشوائية الثابتة الخاصة = عشوائي جديد (47) ؛ حاجز Cyclicbarrier Static الخاص ؛ الحصان العام (Cyclicbarrier B) {Barrier = B ؛ } Override public void run () {try {while (! thread.interrupted ()) {synchronized (this) {// سباق الحصان بشكل عشوائي في عدة خطوات في كل مرة يقومون فيها بتشغيل strides += rand.nextint (3) ؛ } barrier.await () ؛ }} catch (استثناء e) {E.PrintStackTrace () ؛ }} مسارات السلسلة العامة () {StringBuilder s = new StringBuilder () ؛ لـ (int i = 0 ؛ i <getStrides () ؛ i ++) {s.append ("*") ؛ } s.append (id) ؛ إرجاع S.ToString () ؛ } getTrides int المزامنة العامة () {return Strides ؛ } السلسلة العامة toString () {return "Horse" + id + "" ؛ }} ينفذ الفئة العامة porserace Runnable {private static final int finish_line = 75 ؛ قائمة ثابتة خاصة <shrose> Horses = ArrayList New ArrayList <Shors> () ؛ Private Static ExecutorService exec = evectors.newcachedthreadpool () ؛ Override public void run () {StringBuilder s = new StringBuilder () ؛ // print track حدود (int i = 0 ؛ i <finish_line ؛ i ++) {s.append ("=") ؛ } system.out.println (s) ؛ // Print Horse Racing Track لـ (Horse Horse: Horses) {System.out.println (Horse.tracks ()) ؛ } // ناقش ما إذا كان سيتم إنهاء (Horse Horse: Horses) {if (Horse.getStrides ()> = Finish_line) {system.out.println (Horse + "Won!") ؛ exec.shutdownNow () ؛ يعود؛ }} // استراحة الوقت المحدد إلى الجولة التالية Try {timeUnit.milliseconds.sleep (200) ؛ } catch (interruptedException e) {system.out.println ("قاطع الحاجز-العمل") ؛ }} public static void main (string [] args) {cyclicbarrier barrier = new Cyclicbarrier (7 ، New Briderace ()) ؛ لـ (int i = 0 ؛ i <7 ؛ i ++) {Horse Horse = New Horse (Barrier) ؛ الخيول (الحصان) ؛ exec.execute (حصان) ؛ }}}}يستخدم برنامج سباق الخيل هذا بشكل أساسي المسارات الحالية لكل سباق حصان على وحدة التحكم لتحقيق تأثير العرض الديناميكي. هناك جولات متعددة من السباق بأكمله. سيتخذ كل سباق حصان بضع خطوات عشوائية ثم اتصل بالطريقة الانتظار للانتظار. عندما تكمل جميع الخيول جولة واحدة ، سيتم تنفيذ المهمة لطباعة المسار الحالي لجميع سباقات الخيول إلى وحدة التحكم. وبهذه الطريقة ، يستمر مسار كل سباق حصان في كل جولة. عندما يزداد مسار أحد سباقات الخيول أولاً إلى القيمة المحددة ، سينتهي السباق بأكمله ، وسيصبح سباق الخيل هو الفائز بالسباق بأكمله! نتائج تشغيل البرنامج هي كما يلي:
في هذه المرحلة ، سنقارن حتما Cyclicbarrier مع CountDownlatch. يمكن لكلا الفئتين تنفيذ مجموعة من المواضيع تنتظر قبل الوصول إلى حالة معينة. لديهم عداد في الداخل. عندما يتم تقليل قيمة العداد بشكل مستمر إلى 0 ، سيتم إيقاظ جميع خيوط الحظر. الفرق هو أن عداد Cyclicbarrier يتم التحكم فيه من تلقاء نفسه ، في حين يتم التحكم في عداد خطوط التراجع من قبل المستخدم. في Cyclicbarrier ، لن يدعو الخيط إلى أن طريقة الانتظار لن تمنع نفسها فحسب ، بل ستقلل أيضًا من العداد بمقدار 1. في CountDownlatch ، لا يقوم مؤشر الترابط باستدعاء طريقة الانتظار إلا دون تقليل قيمة العداد. بالإضافة إلى ذلك ، يمكن للعد العد التنازلي اعتراض جولة واحدة فقط ، في حين أن Cyclicbarrier يمكن أن ينفذ اعتراض دائري. بشكل عام ، يمكن أن يؤدي استخدام CyclicBarrier إلى تنفيذ وظيفة CountDownlatch ، ولكن لا يمكنه ذلك. على سبيل المثال ، لا يمكن تنفيذ برنامج سباق الخيل أعلاه إلا باستخدام Cyclicbarrier. باختصار ، أوجه التشابه والاختلاف بين هاتين الفئتين هي نفسها تقريبًا. أما بالنسبة لموعد استخدام Cyclicbarrier و CountDownlatch ، فلا يزال يحتاج القراء إلى فهمه بأنفسهم.
ما سبق هو كل محتوى هذه المقالة. آمل أن يكون ذلك مفيدًا لتعلم الجميع وآمل أن يدعم الجميع wulin.com أكثر.