1. اقتراح مشاكل التزامن
لنفترض أننا نستخدم معالجًا ثنائي النواة لتنفيذ مؤشر ترابط A و B ، وتنفيذ Core 1 ، وتنفيذ Core 2 ، Core 2 تنفيذ مؤشر الترابط B ، على كلا المواضيع الآن إضافة 1 إلى متغير العضو I من الكائن المسمى OBJ. على افتراض أن القيمة الأولية لـ I هي 0 ، من الناحية النظرية ، يجب أن تصبح قيمة I 2 بعد تشغيل الخيطين ، ولكن في الواقع من المحتمل جدًا أن تكون النتيجة 1.
دعنا نحلل الأسباب الآن. من أجل بساطة التحليل ، لا نعتبر وضع ذاكرة التخزين المؤقت. في الواقع ، هناك ذاكرة التخزين المؤقت التي ستزيد من إمكانية أن تكون النتيجة هي 1. مؤشر الترابط A يقرأ المتغير I في الذاكرة في وحدة التشغيل الحسابية kernel 1 ، ثم يؤدي عملية الإضافة ، ثم يكتب نتيجة الحساب إلى الذاكرة. نظرًا لأن العملية أعلاه ليست عملية ذرية ، طالما أن الموضوع B يقرأ قيمة I في الذاكرة قبل أن تكتب Thread A قيمة I بإضافة 1 مرة أخرى إلى الذاكرة (قيمة I Is 0 في هذا الوقت) ، فإن نتيجة I سوف تكون بالتأكيد 1.
الحل الأكثر شيوعًا هو استخدام الكلمة الرئيسية المزامنة لقفل كائن OBJ بالرمز الذي يضيف 1 إلى الكود المرئي في موضوعين. نقدم اليوم حلًا جديدًا ، وهو استخدام الفئات ذات الصلة في الحزمة الذرية لحلها.
2. دعم الأجهزة الذروة
في نظام معالج واحد (أحادي المعالج) ، يمكن اعتبار العمليات التي يمكن إكمالها في تعليمات واحدة "العمليات الذرية" لأن المقاطعات لا يمكن أن تحدث إلا بين التعليمات (لأن جدولة مؤشرات الترابط يجب إكمالها من خلال المقاطعات). هذا هو السبب أيضًا في أن بعض أنظمة تعليمات وحدة المعالجة المركزية تقدم Test_and_set و test_and_clear والتعليمات الأخرى لاستبعاد المورد المتبادل. إنه مختلف في الهيكل المتماثل متعدد المعالجات ، نظرًا لأن المعالجات المتعددة تعمل بشكل مستقل في النظام ، حتى العمليات التي يمكن إكمالها في تعليمة واحدة قد تكون مضطربة.
على منصة X86 ، توفر وحدة المعالجة المركزية وسيلة لقفل الحافلة أثناء تنفيذ التعليمات. هناك Lead #Hlockpin على رقاقة وحدة المعالجة المركزية. إذا تمت إضافة البادئة "قفل" إلى تعليمات في برنامج لغة التجميع ، فإن رمز آلة التجميع سيؤدي إلى خفض وحدة المعالجة المركزية من إمكانات #HlockPin عند تنفيذ هذه التعليمات ، وإطلاقها حتى نهاية هذه التعليمات ، وبالتالي قفل الحافلة. وبهذه الطريقة ، لا تستطيع وحدات المعالجة المركزية الأخرى على نفس الحافلة الوصول إلى الذاكرة من خلال الحافلة في الوقت الحالي ، مما يضمن ذرة هذه التعليمات في بيئة متعددة المعالجات. بالطبع ، لا يمكن أن تسبق كل الإرشادات مع القفل. إضافة فقط ، ADC ، و BTC ، BTR ، BTS ، CMPXCHG ، DEC ، INC ، NEG ، NOT ، أو ، أو ، SBB ، SUB ، XOR ، XADD ، و XCHG يمكن أن تسبق مع تعليمات "القفل" لتحقيق العمليات الذرية.
العملية الأساسية للذرية هي CAS (المقارنة ، التي يتم تنفيذها باستخدام تعليمات CMPXCHG ، وهي تعليمات ذرية). تحتوي هذه التعليمات على ثلاثة معاملات ، وهي قيمة الذاكرة V للمتغير (اختصار القيمة) ، والقيمة الحالية المتوقعة E للمتغير (اختصار الاستثناء) ، وقيمة U للمتغير تريد تحديثها (اختصار التحديث). عندما تكون قيمة الذاكرة هي نفس القيمة المتوقعة الحالية ، يتم كتابة القيمة المحدثة للمتغير بواسطة المتغير ، ويتم تنفيذ الرمز الزائف على النحو التالي.
if (v == e) {v = u return true} else {return false}الآن سنستخدم عمليات CAS لحل المشكلات المذكورة أعلاه. يقرأ الخيط B المتغير I في الذاكرة في متغير مؤقت (على افتراض أن القيمة القراءة في هذا الوقت هي 0) ، ثم تقرأ قيمة I في وحدة التشغيل الحسابية لـ Core1. بعد ذلك ، يضيف 1 لمقارنة ما إذا كانت القيمة في المتغير المؤقت هي نفس القيمة الحالية لـ i. إذا كانت قيمة I في الذاكرة هي نفسها مع قيمة النتيجة في وحدة التشغيل (أي I+1) (لاحظ أن هذا الجزء هو عملية CAS ، فهي عملية ذرية ، والتي لا يمكن مقاطعة ولا يمكن تنفيذ عملية CAS في مؤشرات الترابط الأخرى في نفس الوقت) ، وإلا فإن تنفيذ التعليمات فشل. إذا فشلت التعليمات ، فهذا يعني أن مؤشر الترابط A قد زاد من قيمة I بحلول 1. من هذا ، يمكننا أن نرى أنه إذا كانت قيمة القراءة من قبل كلتا الموضوعات هي 0 في البداية ، فإن عملية CAS الخاصة بخيط واحد فقط يمكن أن تكون ناجحة ، لأنه لا يمكن تنفيذ عملية CAS بشكل متزامن. بالنسبة للمواضيع التي تفشل في تنفيذ عمليات CAS ، طالما تم تنفيذ عمليات CAS بشكل خطير ، فستنجح بالتأكيد. يمكنك أن ترى أنه لا يوجد حظر مؤشر ترابط ، وهو ما يختلف بشكل أساسي عن مبدأ التزامن.
3. مقدمة في تحليل الحزمة الذرية ورمز المصدر
الميزة الأساسية للفئة في الحزمة الذرية هي أنه في بيئة متعددة الخيوط ، عندما تعمل مؤشرات ترابط متعددة على متغير واحد (بما في ذلك الأنواع الأساسية وأنواع المراجع) في نفس الوقت ، يكون الحصري ، أي عندما يكون هناك مؤشرات ترابط متعددة تحديث قيمة المتغير في نفس الوقت ، ويمكن أن ينجح مؤشر ترابط واحد فقط ، ويمكن أن يستمر مؤشر الترابط غير الناجح إلى تجربة قفل تدور ، وينتظر إلى أن ينتظر التنفيذ.
ستستدعي الطرق الأساسية في فئة السلسلة الذرية عدة أساليب محلية في الفئة غير الآمنة. نحن بحاجة إلى أن نعرف أولاً أن هناك شيء واحد هو الفصل غير الآمن ، باسمه الكامل: sun.misc.unsafe. تحتوي هذه الفئة على عدد كبير من العمليات على رمز C ، بما في ذلك العديد من مخصصات الذاكرة المباشرة ومكالمات العمليات الذرية. السبب وراء تمييزه باعتباره غير آمن هو إخبارك أن عددًا كبيرًا من مكالمات الطرق في هذا المجال سيكون له مخاطر أمنية ويجب استخدامه بعناية ، وإلا فإنه سيؤدي إلى عواقب وخيمة. على سبيل المثال ، عند تخصيص الذاكرة من خلال غير آمن ، إذا قمت بتحديد مناطق معينة بنفسك ، فقد يتسبب ذلك في عبور الحدود إلى العمليات الأخرى.
يمكن تقسيم الفصول في الحزمة الذرية إلى 4 مجموعات وفقًا لنوع البيانات التشغيلية.
AtomicBoolean,AtomicInteger,AtomicLong
الأنواع الأساسية للعمليات الذرية لآمنة الخيط
AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
التشغيل الذري الآمن لخيط الخيط لنوع الصفيف ، والذي لا يعمل على الصفيف بأكمله ، ولكن على عنصر واحد في الصفيف
AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
عمليات آمنة مؤشرات الترابط على أساس الأنواع الأساسية (عدد صحيح طويل ، عدد صحيح ونوع مرجعي) في كائنات مبدأ الانعكاس
AtomicReference,AtomicMarkableReference,AtomicStampedReference
أنواع مرجعية آمنة للموضوع والعمليات الذرية لأنواع المرجعية التي تمنع مشاكل ABA
نستخدم عمومًا AtomicInteger و AtomicReference و AtomicStampedReference. الآن دعنا نحلل الكود المصدري للأعداد الصحيحة الذرية في الحزمة الذرية. رموز المصدر للفئات الأخرى متشابهة من حيث المبدأ.
1. مُنشئ المعلمة
AtomicInteger العام (int initialValue) {value = initialValue ؛}كما يتضح من وظيفة المنشئ ، يتم تخزين القيمة في القيمة المتغيرة العضو
القيمة المتطايرة الخاصة ؛
يتم الإعلان عن قيمة متغير الأعضاء كنوع متقلبة ، والذي يوضح الرؤية تحت مؤشرات ترابط متعددة ، أي أن تعديل أي مؤشر ترابط سيظهر على الفور في مؤشرات الترابط الأخرى.
2. CompareAnset طريقة (يتم تمرير قيمة القيمة من خلال هذا الداخلية و ValueFfset)
Public Final Boolean CompareAndset (int regite ، int update) {return unsafe.compareanswapint (this ، valueoffset ، توقع ، تحديث) ؛}هذه الطريقة هي أكثر عمليات CAS الأساسية
3. طريقة getandset ، والتي تسمى طريقة المقارنة
public final int getAndet (int newValue) {for (؛؛) {int current = get () ؛ إذا كان (CompareAndSet (الحالي ، newValue)) إرجاع تيار ؛ }}إذا غيرت مؤشرات الترابط الأخرى قيمة القيمة قبل التنفيذ إذا (المقارنات (الحالية ، NewValue) ، يجب أن تكون قيمة القيمة مختلفة عن القيمة الحالية. إذا فشلت المقارنة في التنفيذ ، فيمكنك إعادة الحصول على قيمة القيمة فقط ، ثم متابعة المقارنة حتى تنجح.
4. تنفيذ i ++
Public Final int getAndIncrement () {for (؛؛) {int current = get () ؛ int التالي = الحالي + 1 ؛ if (CompareAndSet (الحالي ، التالي)) إرجاع التيار ؛ }}5. تنفيذ ++ أنا
public final int guyrementandget () {for (؛؛) {int current = get () ؛ int التالي = الحالي + 1 ؛ إذا كان (CompareAndSet (الحالي ، التالي)) يعود بعد ذلك ؛ }}4. استخدم مثال AtomicInteger
يستخدم البرنامج التالي AtomicInteger لمحاكاة برنامج بيع التذاكر. لن يبيع البرنامجان نفس التذكرة في نتيجة التشغيل ، ولن يبيعان التذاكر سلبية.
حزمة javaleanning ؛ استيراد java.util.concurrent.atomic.atomicinteger ؛ فئة عامة selltickets {atomicinteger tickets = new AtomicInteger (100) tickets.get () ؛ if (tickets.compareanset (tmp ، tmp-1)) {system.out.println (thread.currentTherThread (). "Sellera"). start () ؛ موضوع جديد (سانت نويو بائع () ، "SellerB"). Start () ؛}}5. مشكلة أبا
يدير المثال أعلاه النتيجة الصحيحة تمامًا. يعتمد هذا على حقيقة أن مؤشر ترابط (أو أكثر) يعملان على البيانات في نفس الاتجاه. في المثال أعلاه ، يعمل كلا الموضوعين على التذاكر في الانخفاض. على سبيل المثال ، إذا كانت مؤشرات الترابط المتعددة تؤدي عمليات تسجيل الكائنات في قائمة انتظار مشتركة ، فيمكن الحصول على النتائج الصحيحة من خلال فئة AtomicReference (هذا هو الحال في الواقع بالنسبة لقائمة الانتظار المحفوظة في AQS). ومع ذلك ، يمكن تسجيل أو حذف مؤشرات الترابط المتعددة ، أي أن اتجاه تشغيل البيانات غير متسق ، لذلك قد يحدث ABA.
دعنا الآن نأخذ مثالًا سهل الفهم نسبيًا لشرح مشكلة ABA. لنفترض أن هناك خيطان T1 و T2 ، وهذان الخيطان يؤديان عمليات التراص والتكديس على نفس المكدس.
نستخدم الذيل المحدد بواسطة AtomicReference لحفظ الموضع العلوي من المكدس
الذرة <T> الذيل ؛
على افتراض أن مؤشر ترابط T1 جاهز للتشغيل ، لعمليات التراص ، نحتاج فقط إلى تحديث الموضع الأعلى للمكدس من SP إلى الصحف من خلال عملية CAS ، كما هو موضح في الشكل 1. ومع ذلك ، قبل تنفيذ مؤشر ترابط T1 Tail.comPareanset (SP ، Newsp) ، يقوم النظام بإجراء جدولة مؤشرات الترابط ، ويبدأ مؤشر ترابط T2 في التنفيذ. يقوم T2 بإجراء ثلاث عمليات: A خارج المكدس ، B خارج المكدس ، ثم A على المكدس. في هذا الوقت ، يبدأ النظام في الجدولة مرة أخرى ، ويستمر مؤشر ترابط T1 في إجراء عملية التراص ، ولكن في عرض مؤشر ترابط T1 ، لا يزال العنصر الموجود في الجزء العلوي من المكدس (أي ، لا يزال T1 يعتقد أن المكدس لا يزال هو العنصر التالي في الجزء العلوي من المكدس أ) ، ويتم عرض الموقف الفعلي في الشكل 2. يشير المكدس إلى العقدة B. في الواقع ، لم يعد B موجودًا في المكدس. يظهر الشكل 3 النتيجة بعد أن تضع T1 من المكدس في الشكل 3 ، والتي من الواضح أنها ليست النتيجة الصحيحة.
6. حلول لمشاكل ABA
استخدام AtomicMarkablereference ، AtomicStampedReference. استخدم الفئتين الذريتين المذكورة أعلاه لأداء العمليات. عند تنفيذ تعليمات المقارنة ، لا يحتاجون فقط إلى مقارنة القيمة السابقة والقيمة المتوقعة للكائن ، ولكن أيضًا بحاجة إلى مقارنة قيمة الطوابع الحالية (العملية) وقيمة ختم (التشغيل) المتوقعة. فقط عندما يكون كل شيء صحيح ، يمكن أن تنجح طريقة المقارنة. في كل مرة ينجح فيها التحديث ، ستتغير قيمة الطوابع ، ويتم التحكم في إعداد قيمة الطوابع من قبل المبرمج نفسه.
مقارنات منطقية عامة (v requiredreference ، v newReference ، int registtamp ، int newstamp) {pair <V> current = pair ؛ return reportReference == current.reference && reputstamp == current.stamp && (newReferenفي هذا الوقت ، تتطلب طريقة المقارنة أربعة معلمات: المتوقع ، NewReference ، متوقع ، Newstamp. عندما نستخدم هذه الطريقة ، يجب علينا التأكد من أن قيمة الطوابع المتوقعة ليست هي نفس قيمة ختم التحديث. عادة newstamp = متوقع stamp+1
خذ الأمثلة المذكورة أعلاه
افترض أن الخيط T1 هو قبل المكدس: SP يشير إلى A وقيمة الطوابع 100.
يتم تنفيذ الموضوع T2: بعد إصدار A ، نقاط SP إلى B ، وتصبح قيمة الطوابع 101.
بعد إصدار B ، يشير SP إلى C ، وتصبح قيمة الطوابع 102.
بعد وضع A في المكدس ، يشير SP إلى A وتصبح قيمة الطوابع 103.
يستمر الموضوع T1 في تنفيذ بيان المقارنة ويجد أنه على الرغم من أن SP لا يزال يشير إلى A ، فإن القيمة المتوقعة لقيمة الطوابع 100 تختلف عن القيمة الحالية 103. لذلك ، تفشل المقارنة. تحتاج إلى الحصول على قيمة الصحف (في هذا الوقت ، ستشير الصحف إلى C) ، والقيمة المتوقعة لقيمة الطوابع 103 ، ثم تنفيذ عملية المقارنة مرة أخرى. وبهذه الطريقة ، تطرح المكدس بنجاح ، سيشير SP إلى C.
لاحظ أنه نظرًا لأن المقارنة يمكن أن يغير قيمة واحدة فقط في وقت واحد ولا يمكن تغيير NewReference و Newstamp في نفس الوقت ، أثناء التنفيذ ، يتم تعريف فئة الزوج داخليًا لتحويل NewReference و Newstamp إلى كائن واحد. عند إجراء عمليات CAS ، فهي في الواقع عملية على كائن الزوج.
زوج فئة ثابتة خاصة <T> {Final T Reference ؛ ختم int النهائي زوج خاص (مرجع T ، int stamp) {this.reference = reference ؛ this.stamp = Stamp ؛ } static <t> pair <T> من (مرجع t ، int stamp) {return abour new pair <T> (Reference ، Stamp) ؛ }}بالنسبة إلى AtomIcMarkablereference ، تكون قيمة الطوابع متغيرًا منطقيًا ، في حين أن قيمة الطوابع في AtomicStuphReference هي متغير عدد صحيح.
لخص
ما سبق يدور حول النقاش الموجز لهذه المقالة حول مبادئ التنفيذ وتطبيقات الحزم الذرية في جافا. آمل أن يكون ذلك مفيدًا للجميع. يمكن للأصدقاء المهتمين الاستمرار في الرجوع إلى الموضوعات الأخرى ذات الصلة على هذا الموقع. إذا كانت هناك أي أوجه قصور ، فيرجى ترك رسالة لإشارةها.