ما هو قفل الدوران
عند الحديث عن أقفال الدوران ، نحتاج إلى البدء بآلية القفل تحت متعدد الخيوط. نظرًا لأن بعض الموارد في بيئة النظام متعددة المعالجات محدودة ، فإنها تتطلب أحيانًا استبعادًا متبادلًا. في هذا الوقت ، سيتم تقديم آلية قفل. فقط العملية التي تحصل على القفل يمكن أن تحصل على الوصول إلى الموارد. وهذا يعني أن عملية واحدة فقط يمكنها الحصول على القفل في وقت لدخول منطقتها الحرجة. في الوقت نفسه ، لا يمكن لعمليتين أو أكثر دخول المنطقة الحرجة. عند الخروج من المنطقة الحرجة ، سيتم إصدار القفل.
عند تصميم خوارزمية Mutex ، تواجه دائمًا موقفًا لا يكون لديك فيه قفل ، أي ، ماذا يجب أن تفعل إذا لم تحصل على قفل؟
عادة ما تكون هناك طريقتان للتعامل معها:
أحدهما هو أن المتصل الذي لم يحصل على القفل يتجول هناك لمعرفة ما إذا كان حامل قفل الدوران قد أصدر القفل. هذا هو محور هذه المقالة - قفل تدور. ليس عليه منع مدينة الخط (عدم الحظر).
هناك طريقة أخرى هي أن العملية دون الحصول على كتل القفل نفسها (الحظر) وتستمر في تنفيذ مهام أخرى على الخيط ، وهي Mutex (بما في ذلك القفل المدمج المتزامن ، وإعادة إدخاله ، إلخ).
مقدمة
CAS (مقارنة ومبادلة) ، أي المقارنة والتبادل ، هي أيضًا العملية الأساسية التي تنفذ ما نسميه عادة قفل الدوران أو القفل المتفائل.
تطبيقه بسيط للغاية ، وهو مقارنة القيمة المتوقعة مع قيمة الذاكرة. إذا كانت القيمتين متساوية ، استبدل قيمة الذاكرة بالقيمة المتوقعة والإرجاع صحيح. خلاف ذلك ، العودة كاذبة.
ضمان العملية الذرية
تظهر أي تقنية لحل بعض المشكلات المحددة. المشكلة التي تحتاجها CAS لحلها هي ضمان العمليات الذرية. ما هي العملية الذرية؟ الذرات هي الأصغر وغير اللائقة ، والعملية الذرية هي أصغر عملية وغير لائقة. وهذا يعني أنه بمجرد بدء العملية ، لا يمكن مقاطعة ذلك ويعرف أن العملية قد اكتملت. في بيئة متعددة الخيوط ، تعد العمليات الذرية وسيلة مهمة لضمان سلامة الخيوط. على سبيل المثال ، افترض أن هناك خيطان يعملان ويريد تعديل قيمة معينة. خذ العملية الذاتية كمثال. لإجراء عملية التمييز الذاتي على عدد صحيح I ، هناك حاجة إلى ثلاث خطوات أساسية:
1. اقرأ القيمة الحالية لـ i ؛
2. أضف 1 إلى القيمة I ؛
3. اكتب قيمة I مرة أخرى إلى الذاكرة ؛
لنفترض أن كلتا العمليتين اقرأوا القيمة الحالية لـ i ، على افتراض أنه 0 ، في هذا الوقت ، يضيف الخيط A 1 إلى i ، ويضيف الموضوع B أيضًا 1 ، وأخيراً أنا 1 ، وليس 2. هذا لأن عملية التلقائية ليست عملية ذرية ، والخطوات الثلاث المقسمة إلى. كما هو الحال في المثال أدناه ، بالنسبة لـ 10 مؤشرات ترابط ، يقوم كل مؤشر ترابط بإجراء 10000 عملية i ++ ، والقيمة المتوقعة هي 100000 ، ولكن للأسف ، تكون النتيجة دائمًا أقل من 100000.
ثابت int i = 0 ؛ الفراغ الثابت العام add () {i ++ ؛ } الفئة الثابتة الخاصة Plus تنفذ Runnable {Override public void run () {for (int k = 0 ؛ k <10000 ؛ k ++) {add () ؛ }}} static void main (string [] args) يلقي interruptedException {thread [] threads = new thread [10] ؛ لـ (int i = 0 ؛ i <10 ؛ i ++) {threads [i] = new thread (new plus ()) ؛ المواضيع [i] .start () ؛ } لـ (int i = 0 ؛ i <10 ؛ i ++) {threads [i] .join () ؛ } system.out.println (i) ؛ }في هذه الحالة ، ماذا علي أن أفعل؟ هذا صحيح ، ربما كنت قد فكرت بالفعل في الأمر ، يمكنك قفل أو استخدام التنفيذ المتزامن ، على سبيل المثال ، تعديل طريقة الإضافة () إلى ما يلي:
الفراغ الثابت المزامنة العامة add () {i ++ ؛ }بدلاً من ذلك ، يتم تنفيذ عملية القفل ، على سبيل المثال ، باستخدام reentrantlock (reentrantlock).
قفل قفل ثابت خاص = جديد reentrantlock () ؛ public static void add () {lock.lock () ؛ i ++ ؛ lock.unlock () ؛ } CAS تنفذ قفل الدوران
نظرًا لأن العمليات الذرية يمكن تنفيذها باستخدام الكلمة الرئيسية القفل أو المتزامنة ، فلماذا تستخدم CAS؟ نظرًا لأن قفل أو استخدام الكلمات الرئيسية المتزامنة يجلب خسارة كبيرة في الأداء ، في حين أن استخدام CAS يمكن أن يحقق قفلًا متفائلًا. إنه يستخدم مباشرة إرشادات مستوى وحدة المعالجة المركزية ، وبالتالي فإن الأداء مرتفع للغاية.
كما ذكر أعلاه ، CAS هو أساس تنفيذ أقفال الدوران. تستخدم CAS تعليمات وحدة المعالجة المركزية لضمان ذرة العملية لتحقيق تأثير القفل. أما بالنسبة للدوران ، فمن الواضح جدًا قراءة المعنى الحرفي. إذا قمت بتدويرها بنفسك ، فهي حلقة. يتم تنفيذها بشكل عام باستخدام حلقة لا حصر لها. وبهذه الطريقة ، يتم تنفيذ عملية CAS في حلقة لا حصر لها. عندما تكون العملية ناجحة وتعيد صحيحًا ، تنتهي الحلقة ؛ عندما يكون خطأ ، يتم تنفيذ الحلقة وتستمر عملية CAS حتى يتم إرجاع TRUE.
في الواقع ، تستخدم العديد من الأماكن في JDK CAS ، وخاصة في حزمة java.util.concurrent ، مثل CountDownlatch و Semaphore و Reentrantlock و java.util.concurrent.atomic. أعتقد أن كل شخص استخدم Atomic*، مثل AtomicBoolean ، Atomicinteger ، إلخ.
هنا نأخذ AtomicBoolean كمثال ، لأنه بسيط بما فيه الكفاية.
الطبقة العامة AtomicBoolean تنفذ java.io.serializable {private static final long serialversionuid = 4654671469794556979l ؛ // الإعداد لاستخدام unsafe.compareanswapint للحصول على تحديثات خاصة ثابتة غير آمنة غير آمنة = unfafe.getunsafe () ؛ القيمة النهائية الطويلة الثابتة الخاصة ؛ ثابت {try {valueffset = unsafe.objectfieldoffset (AtomicBoolean.class.getDeclaredfield ("value")) ؛ } catch (استثناء ex) {رمي خطأ جديد (ex) ؛ }} قيمة int المتقلبة الخاصة ؛ get boolean get () {قيمة الإرجاع! = 0 ؛ } المقارنات النهائية العامة المقارنة (توقع منطقي ، تحديث منطقي) {int e = توقع؟ 1: 0 ؛ int u = تحديث؟ 1: 0 ؛ إرجاع unsafe.CompareAndSwapint (هذا ، valueffset ، e ، u) ؛ }}هذا جزء من مدونة AtomicBoolean ، ونرى العديد من الأساليب والخصائص الرئيسية هنا.
1. يتم استخدام كائن Sun.Misc.Unsafe. توفر هذه الفئة سلسلة من الطرق لتشغيل كائنات الذاكرة مباشرة ، ولكن يتم استخدامها داخليًا فقط بواسطة JDK ، ولا ينصح بها للمطورين لاستخدامها ؛
2. القيمة تمثل القيمة الفعلية. يمكنك أن ترى أن طريقة GET تحكم في الواقع القيمة المنطقية بناءً على ما إذا كانت القيمة تساوي 0. يتم تعريف القيمة هنا على أنها متقلبة ، لأن المتقلبة يمكن أن تضمن رؤية الذاكرة ، أي طالما تتغير القيمة ، يمكن أن ترى مؤشرات الترابط الأخرى القيمة المتغيرة على الفور. سيتحدث المقالة التالية عن رؤية المتقلبة ، مرحبًا بك في متابعة
3. ValueOffset هو إزاحة الذاكرة لقيمة القيمة ، التي تم الحصول عليها باستخدام طريقة غير آمنة.
4. طريقة المقارنة ، هذه هي الطريقة الأساسية لتنفيذ CAS. عند استخدام طريقة AtomicBoolean ، تحتاج فقط إلى تمرير القيمة المتوقعة والقيمة المراد تحديثها. تسمى طريقة غير آمنة. إنها طريقة أصلية ، يتم تنفيذها في C ++ ، ولن يتم نشر الرمز المحدد. باختصار ، يستخدم تعليمات CMMPXCHG الخاصة بوحدة المعالجة المركزية لإكمال المقارنة والاستبدال. بالطبع ، بناءً على إصدار النظام المحدد ، هناك أيضًا اختلافات في التنفيذ. يمكن للمهتمين البحث عن المقالات ذات الصلة بأنفسهم.
استخدم السيناريوهات
على سبيل المثال ، يمكن استخدام AtomicBoolean في مثل هذا السيناريو. يحتاج النظام إلى تحديد ما إذا كانت بعض عمليات التهيئة تحتاج إلى تنفيذ بناءً على خصائص الحالة لمتغير منطقي. إذا كانت بيئة متعددة الخيوط وتجنب عمليات الإعدام المتكررة ، فيمكن تنفيذها باستخدام AtomicBoolean. الرمز الكاذب كما يلي:
العلم الذري الثابت النهائي الخاص = new AtomicBoolean () ؛ if (flag.compareanset (false ، true)) {init () ؛ }على سبيل المثال ، يمكن استخدام AtomicInteger في العدادات وفي بيئات متعددة الخيوط لضمان عد دقيق.
أسئلة ABA
هناك مشكلة مع CAS ، وهي أن القيمة تتغير من A إلى B ومن ثم من B إلى A. في هذه الحالة ، ستعتقد CAS أن القيمة لم تتغير ، ولكن في الواقع تغيرت. في هذا الصدد ، هناك AtomicStampedReference في ظل حزم متزامنة توفر تنفيذًا بناءً على رقم الإصدار ، والذي يمكن أن يحل بعض المشكلات.
لخص
ما سبق هو المحتوى الكامل لهذه المقالة. آمل أن يكون لمحتوى هذه المقالة قيمة مرجعية معينة لدراسة أو عمل الجميع. إذا كان لديك أي أسئلة ، فيمكنك ترك رسالة للتواصل. شكرا لك على دعمك إلى wulin.com.