عندما لا توفر مكتبة فئة Java أداة مزامنة مناسبة، فأنت بحاجة إلى إنشاء أداة مزامنة مخصصة.
هيكل لمنع العمليات التي تعتمد على الدولة
انسخ رمز الكود كما يلي:
الحصول على القفل في حالة الكائن؛// طلب الحصول على القفل
بينما (الشرط المسبق لا يحمل) {// لم يتم استيفاء الشرط المسبق
تحرير القفل؛// حرر القفل أولاً
انتظر حتى يتم تنفيذ الشرط المسبق؛// انتظر حتى يتم استيفاء الشرط المسبق
يفشل اختياريًا في حالة المقاطعة أو انتهاء المهلة؛// فشل التنفيذ بسبب الانقطاع أو انتهاء المهلة
استعادة القفل؛// أعد محاولة الحصول على القفل
}
تنفيذ الإجراء // تنفيذ
قفل الإصدار؛// قفل الإصدار
مثال على الفئة الأساسية لتنفيذ ذاكرة التخزين المؤقت المقيدة
انسخ رمز الكود كما يلي:
فئة عامة BaseBoundBuffer<V> {
النهائي الخاص V[] buf;
ذيل خاص؛
رأس كثافة العمليات الخاصة؛
عدد صحيح خاص؛
@SuppressWarnings("تم إلغاء التحديد")
عام BaseBoundBuffer(سعة كثافة العمليات) {
buf = (V[]) كائن جديد[سعة];
}
doPut الفراغ المتزامن العام (V v) {
buf[tail] = v;
إذا (++ الذيل == buf.length)
الذيل = 0؛
العد++;
}
العامة المتزامنة V doTake () {
V v = buf[head];
إذا (++head == buf.length)
الرأس = 0؛
عدد--؛
العودة ضد؛
}
القيمة المنطقية المتزامنة النهائية العامة isFull() {
عدد الإرجاع == buf. length;
}
القيمة المنطقية النهائية العامة المتزامنة isEmpty() {
عدد الإرجاع == 0؛
}
}
طريقة تنفيذ الحظر 1: طرح استثناء للمتصل
انسخ رمز الكود كما يلي:
الفراغ العام المتزامن put1 (V v) يلقي استثناء {
إذا (كامل ())
رمي استثناء جديد ("خطأ كامل")؛
doPut(v);
}
التحليل: يجب استخدام الاستثناءات عند حدوث استثناءات، وليس من المناسب طرح الاستثناءات هنا؛ حيث يتعين على المتصل التعامل مع الموقف الذي يفشل فيه الشرط المسبق، وهو ما لا يحل المشكلة الأساسية.
طريقة تنفيذ الحظر 2: من خلال الاقتراع والنوم
انسخ رمز الكود كما يلي:
الفراغ العام put2 (V v) يلقي InterruptedException {
بينما (صحيح) {// الاقتراع
متزامن (هذا) {
إذا (! isFull()) {
doPut(v);
يعود؛
}
}
Thread.sleep(SLEEP_TIME);//Sleep
}
}
التحليل: من الصعب قياس إعداد وقت النوم SLEEP_TIME. إذا كان الإعداد صغيرًا جدًا، فقد تقوم وحدة المعالجة المركزية بالاستقصاء عدة مرات، مما يستهلك المزيد من موارد وحدة المعالجة المركزية؛ وإذا كان الإعداد كبيرًا جدًا، فستكون الاستجابة أقل.
طريقة التنفيذ الثالثة: قائمة الانتظار الشرطية
العناصر الموجودة في قائمة انتظار الشروط هي سلاسل رسائل تنتظر الشروط ذات الصلة واحدًا تلو الآخر. يمكن استخدام كل كائن Java كقفل، ويمكن أيضًا استخدام كل كائن كقائمة انتظار شرطية، وتشكل طرق الانتظار والإخطار والإخطار في الكائن واجهة برمجة التطبيقات لقائمة انتظار الحالة الداخلية. سيقوم Object.wait بتحرير القفل تلقائيًا ويطلب من نظام التشغيل تعليق مؤشر الترابط الحالي حتى تتمكن مؤشرات الترابط الأخرى من الحصول على القفل وتعديل حالة الكائن. يمكن لـ Object.notify وObject.notifyAll تنبيه الخيط المنتظر، وتحديد خيط من قائمة انتظار الشرط للتنبيه ومحاولة استعادة القفل.
انسخ رمز الكود كما يلي:
put3 الفراغ المتزامن العام (V v) يلقي InterruptedException {
بينما (كامل ())
انتظر()؛
دوبوت (الخامس) ؛
notifyAll();
}
التحليل: احصل على استجابة أفضل، بسيطة وسهلة الاستخدام.
استخدم قائمة الانتظار الشرطية
1. المسند الشرطي
1).التعريف: المسند الشرطي هو شرط مسبق لكي تصبح العملية عملية تعتمد على الحالة. المسند الشرطي هو تعبير يتكون من متغيرات الحالة الفردية في الفصل. على سبيل المثال، المسند الشرطي لأسلوب الوضع هو "ذاكرة التخزين المؤقت ليست فارغة".
2).العلاقة: هناك علاقة ثلاثية مهمة في الانتظار المشروط، بما في ذلك القفل، وطريقة الانتظار، والمسند الشرطي. يتم تضمين متغيرات الحالة المتعددة في المسند الشرطي، ويجب حماية كل متغير حالة بواسطة قفل، لذلك يجب الاحتفاظ بالقفل قبل اختبار المسند الشرطي. يجب أن يكون كائن القفل وكائن قائمة الانتظار الشرطية (والكائن حيث يتم استدعاء أساليب الانتظار والإخطار) نفس الكائن.
3). القيود: سيتم ربط كل استدعاء للانتظار ضمنيًا بمسند شرط محدد. عند استدعاء مسند شرط معين، يجب أن يحتفظ المتصل بالفعل بقفل مرتبط بقائمة انتظار الشرط، ويجب أن يحمي هذا القفل أيضًا المسند الشرطي. متغيرات الحالة
2. قواعد استخدام قائمة الانتظار المشروطة
1) عادة ما يكون هناك المسند الشرطي
2).اختبر دائمًا المسندات الشرطية قبل استدعاء الانتظار، ثم اختبرها مرة أخرى بعد العودة من الانتظار؛
3).اتصل دائمًا بالانتظار في الحلقة؛
4). التأكد من أن متغيرات الحالة التي تشكل المسند الشرطي محمية بقفل، ويجب أن يرتبط هذا القفل بقائمة انتظار الشرط؛
5). عند استدعاء الانتظار والإخطار والإخطار للجميع، يجب تعليق القفل المرتبط بقائمة الانتظار الشرطية؛
6). بعد التحقق من المسند الشرطي، لا تقم بتحرير القفل قبل البدء في تنفيذ المنطق المحمي؛
3.الإخطار
حاول استخدام notifyAll بدلاً من nofify لأن nofify سوف يقوم بشكل عشوائي بتنبيه الخيط من الحالة الخاملة إلى الحالة المحظورة (الحالة المحظورة هي سلسلة رسائل تحاول دائمًا الحصول على القفل، أي بمجرد اكتشاف أن القفل متاح، فإنه سيحتفظ بالقفل على الفور)، بينما يقوم notifyAll بإيقاظ جميع سلاسل الرسائل الموجودة في قائمة انتظار الحالة من الحالة الخاملة إلى الحالة المحظورة، ضع في اعتبارك هذا الموقف، إذا دخل الخيط A إلى الحالة الخاملة بسبب المسند الشرطي Pa، والخيط B يدخل في حالة السكون بسبب الشرط المسند Pb صحيح، ينفذ مؤشر الترابط C إشعارًا واحدًا إذا قام JVM باختيار مؤشر الترابط A بشكل عشوائي للتنبيه، ثم يتحقق مؤشر الترابط A من أن المسند الشرطي Pa غير صحيح، ثم يدخل في حالة السكون، ومن ثم فصاعدًا، لا يمكن إيقاظ أي مؤشر ترابط آخر ، وسيكون البرنامج دائمًا في حالة السكون إذا كنت تستخدمه يختلف notifyAll. سوف يقوم JVM بتنبيه جميع سلاسل الرسائل المنتظرة في قائمة انتظار الحالة من حالة السكون إلى الحالة المحظورة. حتى إذا تم تحديد خيط بشكل عشوائي ودخل في حالة السكون لأن المسند الشرطي غير صحيح، فسوف تتنافس سلاسل الرسائل الأخرى على القفل. مواصلة التنفيذ.
4. رمز نسخة النموذج القياسي لطريقة تبعية الحالة هو كما يلي:
حالة باطلةDependentMethod throwsInterruptedException{
متزامن (قفل) {
بينما (! الشرط المسند))
lock.wait();
}
//فعل شيء();
....
notifyAll();
}
إظهار كائن الشرط
يعد كائن الشرط الصريح بديلاً أكثر مرونة ويوفر وظائف أكثر ثراءً: يمكن أن توجد فترات انتظار متعددة على كل قفل، ويمكن أن يكون الانتظار المشروط متقطعًا أو غير متقطع، وانتظارًا يعتمد على الوقت، وعمليات قائمة انتظار عادلة أو غير عادلة. يمكن ربط الشرط بالقفل، تمامًا مثلما ترتبط قائمة الانتظار الشرطية بالقفل المدمج. لإنشاء شرط، قم باستدعاء الأسلوب Lock.newCondition على القفل المرتبط. يتم استخدام الكود التالي لإعادة تنفيذ ذاكرة التخزين المؤقت المحددة باستخدام متغيرات حالة العرض، ويكون الكود كما يلي:
الطبقة العامة ConditionBoundedBuffer<V> {
النهائي الخاص V[] buf;
ذيل خاص؛
رأس كثافة العمليات الخاصة؛
عدد صحيح خاص؛
قفل خاص lock = new ReentrantLock();
حالة خاصة notFullCondition = lock.newCondition();
حالة خاصة notEmptyCondition = lock.newCondition();
@SuppressWarnings("تم إلغاء التحديد")
عامة ConditionBoundedBuffer(سعة كثافة العمليات) {
buf = (V[]) كائن جديد[سعة];
}
الفراغ العام doPut (V v) يلقي InterruptedException {
يحاول {
lock.lock();
بينما (العدد == buf. length)
notFullCondition.await();
buf[tail] = v;
إذا (++ الذيل == buf.length)
الذيل = 0؛
العد++;
notEmptyCondition.signal();
} أخيراً {
lock.unlock();
}
}
يلقي V doTake () العام InterruptedException {
يحاول {
lock.lock();
بينما (العدد == 0)
notEmptyCondition.await();
V v = buf[head];
buf[head] = null;
إذا (++head == buf.length)
الرأس = 0؛
عدد--؛
notFullCondition.signal();
العودة ضد؛
} أخيراً {
lock.unlock();
}
}
}