0. حول mutex
يشير قفل Mutex المزعوم إلى قفل يمكن أن يكون له خيط واحد فقط في وقت واحد. قبل JDK1.5 ، عادة ما استخدمنا الآلية المتزامنة للتحكم في وصول مؤشرات الترابط المتعددة إلى الموارد المشتركة. الآن ، يوفر Lock مجموعة واسعة من عمليات القفل من الآلية المتزامنة. الاختلافات الرئيسية بين القفل والآليات المتزامنة:
توفر الآلية المتزامنة إمكانية الوصول إلى أقفال الشاشة الضمنية المرتبطة بكل كائن ، ويجبر جميع عمليات الاستحواذ والإفراج على القفل في بنية كتلة. عند الحصول على أقفال متعددة ، يجب إطلاقها بترتيب عكسي. الآلية المتزامنة تطلق الأقفال ضمنيًا. طالما أن الكود الذي يتم تشغيله بواسطة مؤشر الترابط يتجاوز نطاق كتلة العبارة المتزامنة ، سيتم إصدار القفل. يجب أن تستدعي آلية القفل بشكل صريح طريقة Unlock () لكائن القفل لإطلاق القفل ، والذي يوفر إمكانية اكتساب وإصدار الأقفال بعدم الظهور في نفس بنية الكتلة ، وإطلاق الأقفال بترتيب مجاني أكثر.
1.
REENTRANTLOCK هو قفل MUTEX REENTRANT ، والمعروف أيضًا باسم "القفل الحصري".
كما يوحي الاسم ، لا يمكن الاحتفاظ بقفل reentrantlock إلا بواسطة قفل مؤشر ترابط واحد في نفس النقطة الزمنية ؛ في حين أن Reentrant يعني أنه يمكن الحصول على قفل إعادة إدخال عدة مرات بواسطة موضوع واحد.
ينقسم Reentrantlock إلى "قفل عادل" و "قفل غير عادل". تنعكس خلافاتهم في ما إذا كانت آلية الحصول على الأقفال نزيهة. "القفل" هو حماية الموارد المتنافسة ومنع مؤشرات ترابط متعددة من سلاسل التشغيل في نفس الوقت والأخطاء. لا يمكن الحصول على REENTRANTLOCK إلا بواسطة مؤشر ترابط واحد في نفس الوقت (عندما يكتسب مؤشر ترابط "القفل" ، يجب أن تنتظر مؤشرات الترابط الأخرى) ؛ تدير Reentraantlock جميع المواضيع التي تحصل على القفل من خلال قائمة انتظار انتظار FIFO. تحت آلية "القفل العادل" ، فإن الخيوط قائمة الانتظار للحصول على التسلسل في التسلسل ؛ في حين أن "قفل غير القفل" سيحصل على القفل بغض النظر عما إذا كان في بداية قائمة الانتظار أم لا.
قائمة وظائف REENTRANTLOCK
// إنشاء reentrantlock ، وهو "قفل غير عادل" افتراضيًا. reentrantlock () // سياسة الخلق هي إعادة إدخال المعرض. إذا كان Fair صحيحًا ، فهذا يعني أنه قفل عادل ، وإذا كان Fair False ، فهذا يعني أنه قفل غير مشترك. REENTRANTLOCK (معرض منطقي) // الاستعلام عن عدد المرات التي حافظ فيها الخيط الحالي على هذا القفل. int getholdcount () // إرجاع الخيط الذي يمتلك هذا القفل حاليًا ، وإذا لم يكن هذا القفل مملوكًا لأي مؤشر ترابط ، فالارتداد. Thread GetOwner () // إرجاع مجموعة تحتوي على الخيط الذي قد ينتظر الحصول على هذا القفل. مجموعة محمية <Hrose> getQueuedThreads () // إرجاع العدد المقدر من مؤشرات الترابط في انتظار الحصول على هذا القفل. int getQueuelength () // إرجاع مجموعة تحتوي على تلك المواضيع التي قد تنتظر شرط معين يتعلق بهذا القفل. مجموعة محمية <froof> getWaitingThreads (حالة الشرط) // إرجاع تقدير مؤشر الترابط في انتظار الشرط المحدد المرتبط بهذا القفل. int getWaitqueuelength (حالة الشرط) // استعلام ما إذا كان الخيط المعطى ينتظر الحصول على هذا القفل. Boolean HasqueuedThread (Thread Thread) // Query ما إذا كانت بعض المواضيع تنتظر الحصول على هذا القفل. Boolean HasqueuedThreads () // Query ما إذا كانت بعض المواضيع تنتظر شرط معين يتعلق بهذا القفل. Boolean Haswaiters (حالة الحالة) // إرجاع صحيح إذا كان "قفلًا عادلًا" ، وإلا فإن العودة خاطئة. Boolean Isfair () // Query ما إذا كان الخيط الحالي يحافظ على هذا القفل. Boolean IsheldbyCurrentTherThread () // Query ما إذا كان هذا القفل محتجزًا بواسطة أي موضوع. Boolean Islocked () // الحصول على القفل. قفل void () // إذا لم يتم مقاطعة الخيط الحالي ، فسيتم الحصول على القفل. void lockinterruptly () // إرجاع مثيل الشرط المستخدم لاستخدامه مع مثيل القفل هذا. شرط NewCondition () // فقط الحصول على القفل إذا لم يتم الاحتفاظ به بواسطة موضوع آخر أثناء المكالمة. Boolean Trylock () // إذا لم يتم الاحتفاظ بالقفل بواسطة مؤشر ترابط آخر في وقت انتظار معين ولم يتم مقاطعة الخيط الحالي ، فسيتم الحصول على القفل. Boolean Trylock (Timeout ، وحدة TimeUnit) // حاول إطلاق هذا القفل. فتح الفراغ ()
2
بمقارنة "المثال 1" و "المثال 2" ، يمكننا أن نفهم بوضوح دور القفل والفتح
2.1 مثال 1
استيراد java.util.concurrent.locks.lock ؛ استيراد java.util.concurrent.locks.reentrantlock ؛ // locktest1.java// مستودع فئة المستودع {private int size ؛ // العدد الفعلي لقفل المستودع الخاص. // الحصري قفل public depot () {this.size = 0 ؛ this.lock = جديد reentrantlock () ؛ } public void produce (int val) {lock.lock () ؛ حاول {size += val ؛ system.out.printf ("٪ s Produce (٪ d) -> size = ٪ d/n" ، thread.currentTherThread (). getName () ، val ، size) ؛ } أخيرًا {lock.unlock () ؛ }} استهلاك الباطل العام (int val) {lock.lock () ؛ حاول {size -= val ؛ system.out.printf ("٪ s استهلاك (٪ d) <- size = ٪ d/n" ، thread.currentThRead (). getName () ، val ، size) ؛ } أخيرًا {lock.unlock () ؛ }}} ؛ // منتج فئة المنتج {Private Depot Deport ؛ المنتج العام (Depot Depot) {this.depot = الإيداع ؛ } // منتجات المستهلك: قم بإنشاء موضوع جديد لإنتاج منتجات في المستودع. Public Void Produce (Final int val) {new thread () {public void run () {despars.produce (val) ؛ } }.يبدأ()؛ }} // Consumer Class Customer {Private Depot Deposit ؛ العميل العام (Depot Depot) {this.depot = الإيداع ؛ } // منتج المستهلك: قم بإنشاء مؤشر ترابط جديد لاستهلاك المنتج من المستودع. استهلاك الفراغ العام (النهائي int val) {new thread () {public void run () {depot.consume (val) ؛ } }.يبدأ()؛ }} الفئة العامة locktest1 {public static void main (string [] args) {depot mdepot = new depot () ؛ منتج MPRO = منتج جديد (MDEPOT) ؛ MCUs العميل = عميل جديد (MDEPOT) ؛ mpro.produce (60) ؛ mpro.produce (120) ؛ mcus.consume (90) ؛ mcus.consume (150) ؛ mpro.produce (110) ؛ }} نتائج التشغيل:
Thread-0 Prode (60)-> Size = 60Thread-1 Prode (120)-> Size = 180Thread-3 يستهلك (150) <-الحجم = 30Thread-2 يستهلك (90) <-Size = -60thread-4 Produce (110)-> الحجم = 50
تحليل النتائج:
(1) المستودع هو مستودع. يمكن إنتاج البضائع في المستودع من خلال المنتجات () ، ويمكن استهلاك البضائع الموجودة في المستودع من خلال الاستهلاك (). يتم تحقيق الوصول الحصري للطرفين إلى المستودع من خلال قفل القفل الحصري: قبل تشغيل البضائع في المستودع (الإنتاج/الاستهلاك) ، سيتم إغلاق المستودع من خلال القفل () أولاً ، ثم يتم إلغاء قفله من خلال فتح () بعد اكتمال العملية.
(2) المنتج هو منتج. يمكن لاستدعاء وظيفة Produce () في المنتج إنشاء مؤشر ترابط جديد لإنتاج منتجات في المستودع.
(3) العميل هو فئة المستهلك. يمكن استدعاء وظيفة الاستهلاك () في العميل إنشاء منتج جديد استهلاك مؤشرات الترابط في المستودع.
(4) في الخيط الرئيسي الرئيسي ، سننشئ منتجًا جديدًا MPRO و MCUs المستهلك الجديد. أنها تنتج/استهلاك المنتجات في المستودعات على التوالي.
وفقًا لكمية الإنتاج/الاستهلاك في MAIN ، يجب أن يكون المنتج النهائي المتبقي في المستودع 50 عامًا. تتماشى نتائج العملية مع توقعاتنا!
هناك مشكلتان في هذا النموذج:
(1) في الواقع ، لا يمكن أن تكون قدرة المستودع سلبية. ومع ذلك ، فإن قدرة المستودع في هذا النموذج يمكن أن تكون سلبية ، والتي تتناقض مع الواقع!
(2) في الواقع ، تكون قدرة المستودع محدودة. ومع ذلك ، لا يوجد حد حقًا للقدرة في هذا النموذج!
سنتحدث بإيجاز عن كيفية حل هاتين المشكلتين. الآن ، دعونا نلقي نظرة على مثال بسيط 2 أولاً ؛ بمقارنة "المثال 1" و "المثال 2" ، يمكننا أن نفهم الغرض من Lock () وفتح () بشكل أكثر وضوحًا.
2.2 مثال 2
استيراد java.util.concurrent.locks.lock ؛ استيراد java.util.concurrent.locks.reentrantlock ؛ // locktest2 // العدد الفعلي لقفل المستودع الخاص. // الحصري قفل public depot () {this.size = 0 ؛ this.lock = جديد reentrantlock () ؛ } Public Void Produce (int val) {// lock.lock () ؛ // try {size += val ؛ System.out.printf ("٪ s Produce (٪ d) -> size = ٪ d/n" ، thread.currentTheread (). getName () ، val ، size) ؛ //} catch (interruptedException e) {//} أخيرًا {// lock.unlock () ؛ //}} adult public (int val) System.out.printf ("٪ s تستهلك (٪ d) <- size = ٪ d/n" ، thread.currentThread (). getName () ، val ، size) ؛ //} أخيرًا {// lock.unlock () ؛ //}} ؛ المنتج العام (إيداع المستودع) {this.depot = الإيداع ؛ } // منتج المستهلك: قم بإنشاء مؤشر ترابط جديد لإنتاج المنتج في المستودع. Public Void Produce (Final int val) {new thread () {public void run () {despars.produce (val) ؛ } }.يبدأ()؛ }} // Consumer Class Customer {Private Depot Deposit ؛ العميل العام (Depot Depot) {this.depot = الإيداع ؛ } // منتج المستهلك: قم بإنشاء مؤشر ترابط جديد لاستهلاك المنتج من المستودع. استهلاك الفراغ العام (النهائي int val) {new thread () {public void run () {depot.consume (val) ؛ } }.يبدأ()؛ }} الفئة العامة locktest2 {public static void main (string [] args) {depot mdepot = new depot () ؛ منتج MPRO = منتج جديد (MDEPOT) ؛ MCUs العميل = عميل جديد (MDEPOT) ؛ mpro.produce (60) ؛ mpro.produce (120) ؛ mcus.consume (90) ؛ mcus.consume (150) ؛ mpro.produce (110) ؛ }} (مرة واحدة) النتيجة:
Thread-0 Prode (60)-> Size = -60Thread-4 Produce (110)-> Size = 50Thread-2 الاستهلاك (90) <-Size = -60Thread-1 Produce (120)-> Size = -60Thread-3 Crية (150) <-Size = -60)
وصف النتائج:
"مثال 2" يزيل القفل بناءً على "مثال 1". في المثال 2 ، المنتج النهائي المتبقي في المستودع هو -60 ، وليس الـ 50 الذي توقعناه. والسبب هو أننا لا ننفذ الوصول إلى Mutex إلى المستودع.
2.3 مثال 3
في "المثال 3" ، نستخدم حالة لحل مشكلتين في "المثال 1": "لا يمكن أن تكون قدرة المستودع سلبية" و "قدرة المستودع محدودة".
الحل لهذه المشكلة هو من خلال الحالة. يجب استخدام الحالة بالاقتران مع القفل: يمكن أن تتسبب طريقة AWAIT () في حالة في حظر الخيط [على غرار WAIT ()] ؛ يمكن أن تتسبب طريقة الإشارة () للشرط في أن يتسبب مؤشر ترابط الاستيقاظ في [مشابه للإخطار ()].
استيراد java.util.concurrent.locks.lock ؛ استيراد java.util.concurrent.locks.reentrantlock ؛ استيراد java.util.concurrent.locks.condition ؛ // locktest3 // سعة المستودع حجم int الخاص ؛ // العدد الفعلي لقفل القفل الخاص بالمستودعات ؛ // قفل الحصري شرط خاص // ظروف الإنتاج حالة خاصة شرط فارغ ؛ // شروط الاستهلاك المستودع العام (السعة int) {this.capacity = السعة ؛ this.size = 0 ؛ this.lock = جديد reentrantlock () ؛ this.fullCondtion = lock.newcondition () ؛ this.emptycondition = lock.newcondition () ؛ } public void produce (int val) {lock.lock () ؛ جرب {// اليسار يعني "الكمية التي تريد إنتاجها" (قد يكون إنتاجًا كبيرًا ، لذلك تحتاج إلى إنتاج المزيد) int left = val ؛ بينما (LEFT> 0) {// عندما يكون المخزون ممتلئًا ، انتظر حتى يستهلك "المستهلك" المنتج. بينما (الحجم> = السعة) fullcondtion.await () ؛ // احصل على "كمية الإنتاج الفعلية" (أي الكمية الجديدة المضافة في المخزون) // إذا "المخزون" + "كمية الإنتاج المطلوبة"> "السعة الكلية" ، ثم "الزيادة الفعلية" = "السعة الكلية" - "السعة الحالية". (املأ المستودع في هذا الوقت) // وإلا (حجم السعة): اليسار ؛ الحجم += INC ؛ اليسار -= INC ؛ System.out.printf ("٪ s Produce (٪ 3D) -> left = ٪ 3D ، inc = ٪ 3D ، size = ٪ 3d/n" ، thread.currentTherAd (). getName () ، val ، left ، inc ، size) ؛ // إخطار "المستهلك" الذي يمكنك استهلاكه. فارغ condtion.signal () ؛ }} catch (interruptedException e) {} أخيرًا {lock.unlock () ؛ }} استهلاك الباطل العام (int val) {lock.lock () ؛ جرب {// اليسار يعني "مقدار الاستهلاك الذي يجب استهلاكه" (قد يكون كبيرًا جدًا ، والمخزون لا يكفي ، لذلك تحتاج إلى استهلاك المزيد) int left = val ؛ بينما (LEFT> 0) {// عندما يكون المخزون 0 ، انتظر "المنتج" لإنتاج المنتج. بينما (الحجم <= 0) فارغة. // احصل على "كمية الاستهلاك الفعلي" (أي الانخفاض الفعلي في المخزون) // إذا كان "المخزون" <"الكمية التي يريد العميل أن يستهلكها" ، ثم "الاستهلاك الفعلي" = "المخزون" ؛ // خلاف ذلك ، "الاستهلاك الفعلي" = "الكمية التي يريد العميل استهلاكها". int dec = (الحجم <يسار)؟ الحجم: اليسار ؛ الحجم -= ديسمبر ؛ اليسار -= ديسمبر ؛ system.out.printf ("٪ s يستهلك (٪ 3D) <- اليسار = ٪ 3D ، ديسمبر = ٪ 3D ، الحجم = ٪ 3d/n" ، thread.currentTherD (). getName () ، val ، اليسار ، ديسمبر ، الحجم) ؛ fullcondtion.signal () ؛ }} catch (interruptedException e) {} أخيرًا {lock.unlock () ؛ }} السلسلة العامة toString () {return "السعة:"+سعة+"، الحجم الفعلي:"+حجم ؛ }} ؛ // منتج فئة المنتج {Private Depot Deposit ؛ المنتج العام (إيداع المستودع) {this.depot = الإيداع ؛ } // منتج المستهلك: قم بإنشاء مؤشر ترابط جديد لإنتاج المنتج في المستودع. Public Void Produce (Final int val) {new thread () {public void run () {despars.produce (val) ؛ } }.يبدأ()؛ }} // Consumer Class Customer {Private Depot Deposit ؛ العميل العام (Depot Depot) {this.depot = الإيداع ؛ } // منتج المستهلك: قم بإنشاء مؤشر ترابط جديد لاستهلاك المنتج من المستودع. استهلاك الفراغ العام (النهائي int val) {new thread () {public void run () {depot.consume (val) ؛ } }.يبدأ()؛ }} الفئة العامة locktest3 {public static void main (string [] args) {depot mdepot = new depot (100) ؛ منتج MPRO = منتج جديد (MDEPOT) ؛ MCUs العميل = عميل جديد (MDEPOT) ؛ mpro.produce (60) ؛ mpro.produce (120) ؛ mcus.consume (90) ؛ mcus.consume (150) ؛ mpro.produce (110) ؛ }} (مرة واحدة) النتيجة:
Thread-0 Prode (60)-> Left = 0 ، inc = 60 ، Size = 60Thread-1 Produe (120)-> Left = 80 ، inc = 40 ، size = 100thread-2 الاستهلاك (90) <-اليسار = 0 ، ديسمبر = 90 ، الحجم = 10thread-3 الاستهلاك (-150) الاستهلاك (150) <-اليسار = 40 ، ديسمبر = 100 ، الحجم = 0thread-4 المنتج (110)-> اليسار = 0 ، inc = 10 ، الحجم = 10thread-3 يستهلك (150) <-اليسار = 30 ، ديسمبر = 10 ، الحجم = 0 ، ديسمبر -1 = 30)