يمكن اعتبار المتغير المتطاير في لغة Java "متزامنًا أخف" ؛ بالمقارنة مع الكتلة المتزامنة ، يتطلب المتغير المتطرف أقل ترميزًا ووقت تشغيل ، ولكن الوظيفة التي يمكن أن تحققها ليست سوى جزء من متزامن.
قفل
توفر الأقفال ميزتين رئيسيتين: الاستبعاد المتبادل والرؤية.
المتغيرات المتقلبة
المتغير المتطاير لديه خصائص رؤية متزامنة ، ولكن ليس لها الخصائص الذرية. هذا يعني أن الخيط يمكنه تلقائيًا اكتشاف أحدث قيمة للمتغير المتطاير.
يمكن استخدام المتغيرات المتطايرة لتوفير سلامة مؤشرات الترابط ، ولكن لا يمكن تطبيقها إلا على مجموعة محدودة للغاية من حالات الاستخدام: لا يوجد أي قيد بين متغيرات متعددة أو بين القيمة الحالية للمتغير والقيمة المعدلة. لذلك ، فإن استخدام المتقلبة وحدها لا يكفي لتنفيذ العدادات أو الطفرة أو أي فئة ذات ثبات مرتبطين بمتغيرات متعددة (على سبيل المثال "ابدأ <= النهاية").
من أجل البساطة أو قابلية التوسع ، قد تميل إلى استخدام المتغيرات المتقلبة بدلاً من الأقفال. من الأسهل تشفير وقراءة بعض التعبيرات عند استخدام المتغيرات المتطايرة بدلاً من الأقفال. بالإضافة إلى ذلك ، لا يتسبب المتغير المتطاير في انسداد مؤشر الترابط مثل القفل ، وبالتالي نادرًا ما يسبب مشاكل قابلية التوسع. في بعض الحالات ، يمكن أن يوفر المتغير المتطاير أيضًا مزايا الأداء على الأقفال إذا كانت عملية القراءة أكبر بكثير من عملية الكتابة.
شروط الاستخدام الصحيح للمتغيرات المتطايرة
يمكنك فقط استخدام المتغير المتقلب بدلاً من الأقفال في الحالات المحدودة. لجعل سلامة الخيط المثالية المتغير المتغير ، يجب تلبية الشرطين التاليين في نفس الوقت:
لا تعتمد عمليات الكتابة على المتغيرات على القيمة الحالية.
لا يتم تضمين هذا المتغير في المتغيرات الأخرى.
في الواقع ، تشير هذه الشروط إلى أن هذه القيم الصحيحة التي يمكن كتابتها إلى المتغير المتقلبة مستقلة عن حالة أي برنامج ، بما في ذلك الحالة الحالية للمتغير.
تمنع حدود الشرط الأول المتغير المتقلبة من استخدامه كضكانة آمنة لخيط الخيط. على الرغم من أن عملية تدريجية (X ++) تبدو وكأنها عملية منفصلة ، إلا أنها في الواقع عملية مشتركة تتكون من سلسلة من العمليات والكتابة المعدلة التي يجب أن يتم تنفيذها ذريًا ، ولا يمكن أن توفر المتقلبة الخصائص الذرية اللازمة. يتطلب تنفيذ العملية الصحيحة الحفاظ على قيمة X ثابتة أثناء العملية ، وهو أمر غير ممكن مع المتغير المتطاير. (ومع ذلك ، إذا تم ضبط القيمة لتكون مكتوبة فقط من موضوع واحد ، يمكن تجاهل الشرط الأول.)
تتعارض معظم مواقف البرمجة مع أحد هاتين الشرطين ، مما يجعل المتغير المتقلب لا ينطبق عالميًا على سلامة الخيوط كما هو متزامن. يوضح القائمة 1 فئة النطاق العددي غير الآمن. أنه يحتوي على ثابت - الحد الأدنى دائمًا أقل من أو يساوي الحد الأعلى.
إعطاء مثال
دعونا نرى مثالا أدناه. نحن ننفذ عداد. في كل مرة يبدأ مؤشر الترابط ، سيتم استدعاء طريقة Counter Inc لإضافة العداد إلى بيئة التنفيذ - إصدار JDK: JDK1.6.0_31 ، الذاكرة: 3G CPU: x86 2.4g
عداد الفئة العامة {العدد الثابت العام = 0 ؛ Public Static Void Inc () {// التأخير هنا هو 1 مللي ثانية ، مما يجعل النتيجة واضحة المحاولة {thread.sleep (1) ؛ } catch (interruptedException e) {} count ++ ؛ } main static void main (string [] args) {// ابدأ 1000 مؤشر ترابط في نفس الوقت لإجراء حسابات i ++ ورؤية النتيجة الفعلية لـ (int i = 0 ؛ i <1000 ؛ i ++) {new thread (new Runnable () {Override public void run () {counter.inc () ؛}}}). start () ؛ } // قد تكون قيمة كل تشغيل هنا مختلفة ، ربما 1000 System.out.println ("Run Running: counter.count =" + counter.count) ؛ }} النتيجة الجري: counter.count = 995
قد تكون نتيجة العملية الفعلية مختلفة في كل مرة. نتيجة الجهاز هي: النتيجة تشغيل: counter.count = 995. يمكن أن نرى أنه في بيئة متعددة الخيوط ، لا يتوقع Count أن تكون النتيجة 1000.
يعتقد الكثير من الناس أن هذه مشكلة تزامن متعددة الخيوط. تحتاج فقط إلى إضافة متقلبة قبل عدد المتغير لتجنب هذه المشكلة. ثم نقوم بتعديل الكود لمعرفة ما إذا كانت النتيجة تلبي توقعاتنا.
عداد الطبقة العامة {العدد الثابت العام الثابت = 0 ؛ Public Static Void Inc () {// التأخير هنا هو 1 مللي ثانية ، مما يجعل النتيجة واضحة المحاولة {thread.sleep (1) ؛ } catch (interruptedException e) {} count ++ ؛ } main static void main (string [] args) {// ابدأ 1000 مؤشر ترابط في نفس الوقت ، قم بإجراء حسابات i ++ ، وشاهد النتيجة الفعلية لـ (int i = 0 ؛ i <1000 ؛ i ++) {new thread (new Runnable () {Override public void run () {counter.inc () ؛}}}). start () ؛ } // قد تكون قيمة كل تشغيل هنا مختلفة ، وربما 1000 System.out.println ("Run Running: counter.count =" + counter.count) ؛ }}نتيجة التشغيل: counter.count = 992
لا تزال نتيجة العملية ليست 1000 كما توقعنا. دعونا نحلل الأسباب أدناه
في مقالة مجموعة Java Garbage ، تم وصف تخصيص الذاكرة في لحظة JVM. واحدة من مناطق الذاكرة هي مكدس الجهاز الافتراضي JVM. يحتوي كل مؤشر ترابط على مكدس مؤشر ترابط عند تشغيله ، ويحفظ مكدس مؤشر الترابط معلومات القيمة المتغيرة أثناء تشغيل مؤشر الترابط. عندما يقوم مؤشر ترابط بالوصول إلى قيمة كائن معين ، فإنه يعثر أولاً على قيمة المتغير المقابل لذاكرة الكومة من خلال مرجع الكائن ، ثم يقوم بتحميل القيمة المحددة لمتغير ذاكرة الكومة في الذاكرة المحلية للمعلومات لإنشاء نسخة متغيرة. بعد ذلك ، لم يعد مؤشر ترابط أي علاقة مع قيمة متغير ذاكرة الكائن للكائن ، ولكنه يعدل مباشرة قيمة متغير النسخ ، وفي لحظة معينة بعد التعديل (قبل خروج مؤشر الترابط) ، يكتب تلقائيًا قيمة نسخ متغير مؤشر الترابط إلى متغير كومة الكائن. بهذه الطريقة ستتغير قيمة الكائن في الكومة. تصف الصورة التالية تفاعل هذه الكتابة
EAD وتحميل متغيرات نسخ من الذاكرة الرئيسية إلى الذاكرة العاملة الحالية
استخدم وتعيين رمز لتغيير القيمة المتغيرة المشتركة
تخزين وكتابة تحديث المحتوى الرئيسي ذي الذاكرة مع بيانات الذاكرة العاملة
حيث يمكن أن يظهر الاستخدام والتعيين عدة مرات
ومع ذلك ، فإن هذه العمليات ليست ذرية ، أي بعد تحميل القراءة ، إذا تم تعديل متغير عدد الذاكرة الرئيسي ، فلن تسبب القيمة في ذاكرة العمل في مؤشر الترابط تغييرات مقابلة لأنه تم تحميله ، لذلك ستكون النتيجة المحسوبة مختلفة عن المتوقع.
بالنسبة للمتغيرات التي تم تعديلها بواسطة متقلبة ، يضمن جهاز JVM الظاهري فقط أن القيمة المحملة من الذاكرة الرئيسية إلى ذاكرة العمل هي الأحدث
على سبيل المثال ، إذا كان مؤشر الترابط 1 و Prohet 2 يقومان بإجراء عمليات القراءة والتحميل ، وابحث عن أن قيمة العد في الذاكرة الرئيسية هي 5 ، فسيتم تحميل أحدث قيمة
بعد تعديل عدد الكومة في الموضوع 1 ، سيتم كتابته في الذاكرة الرئيسية ، وسيصبح متغير العد في الذاكرة الرئيسية 6.
نظرًا لأن Thread 2 قام بالفعل بإجراء عملية القراءة والتحميل ، سيتم أيضًا تحديث القيمة المتغيرة لعدد الذاكرة الرئيسي إلى 6 بعد العملية.
يؤدي هذا إلى حدوث التزامن بعد تعديل خيطين مع الكلمة الرئيسية المتطايرة في الوقت المناسب.