في المعالجة المتزامنة لسلسلة محادثات Java، يوجد حاليًا الكثير من الالتباس حول استخدام الكلمة الأساسية المتقلبة، ويعتقد أنه باستخدام هذه الكلمة الأساسية، سيكون كل شيء على ما يرام عند إجراء معالجة متزامنة متعددة الخيوط.
تدعم لغة Java تعدد الخيوط من أجل حل مشكلة توافق الخيط، يتم تقديم آليات الكتلة المتزامنة والكلمات الرئيسية المتطايرة داخل اللغة.
متزامن
الجميع على دراية بالكتل المتزامنة، والتي يتم تنفيذها من خلال الكلمة الأساسية المتزامنة. مع إضافة عبارات متزامنة وكتلة، عند الوصول إليها بواسطة سلاسل رسائل متعددة، يمكن لخيط واحد فقط استخدام الأساليب المعدلة المتزامنة أو كتل التعليمات البرمجية في نفس الوقت.
متقلب
بالنسبة للمتغيرات المعدلة باستخدام المتطايرة، في كل مرة يستخدم فيها مؤشر الترابط المتغير، فإنه سيقرأ القيمة المعدلة للمتغير. يمكن بسهولة إساءة استخدام المواد المتطايرة لإجراء العمليات الذرية.
دعونا نلقي نظرة على المثال أدناه. نقوم بتنفيذ عداد في كل مرة يبدأ فيها الخيط، سيتم استدعاء طريقة counter inc لإضافة عداد إلى العداد.
بيئة التنفيذ - إصدار jdk: jdk1.6.0_31، الذاكرة: وحدة المعالجة المركزية 3G: x86 2.4G
انسخ رمز الكود كما يلي:
عداد الطبقة العامة {
عدد العدد الثابت العام = 0؛
الفراغ الثابت العام المؤتمر الوطني العراقي () {
// تأخير 1 مللي ثانية هنا لجعل النتيجة واضحة
يحاول {
Thread.sleep(1);
} قبض على (InterruptedException e) {
}
العد++;
}
public static void main(String[] args) {
// ابدأ 1000 موضوع في نفس الوقت لإجراء حسابات i++ ورؤية النتائج الفعلية
لـ (int i = 0; i < 1000; i++) {
موضوع جديد (جديد قابل للتشغيل () {
@تجاوز
تشغيل الفراغ العام () {
Counter.inc();
}
}).يبدأ()؛
}
// قد تختلف القيمة هنا في كل مرة يتم تشغيلها، ربما 1000
System.out.println("نتيجة التشغيل: Counter.count=" + Counter.count);
}
}
نتيجة التشغيل: Counter.count=995
قد تختلف نتيجة العملية الفعلية في كل مرة. نتيجة هذا الجهاز هي: نتيجة التشغيل: Counter.count=995 يمكن ملاحظة أنه في بيئة متعددة الخيوط، لا يتوقع Counter.count أن تكون النتيجة 1000.
يعتقد الكثير من الناس أن هذه مشكلة التزامن متعدد الخيوط، ما عليك سوى إضافة متغير قبل عدد المتغيرات لتجنب هذه المشكلة، ثم سنقوم بتعديل الكود لمعرفة ما إذا كانت النتيجة تلبي توقعاتنا.
انسخ رمز الكود كما يلي:
عداد الطبقة العامة {
عدد int الثابت العام المتقلب = 0 ؛
الفراغ الثابت العام المؤتمر الوطني العراقي () {
// تأخير 1 مللي ثانية هنا لجعل النتيجة واضحة
يحاول {
Thread.sleep(1);
} قبض على (InterruptedException e) {
}
العد++;
}
public static void main(String[] args) {
// ابدأ 1000 موضوع في نفس الوقت لإجراء حسابات i++ ورؤية النتائج الفعلية
لـ (int i = 0; i < 1000; i++) {
موضوع جديد (جديد قابل للتشغيل () {
@تجاوز
تشغيل الفراغ العام () {
Counter.inc();
}
}).يبدأ()؛
}
// قد تختلف القيمة هنا في كل مرة يتم تشغيلها، ربما 1000
System.out.println("نتيجة التشغيل: Counter.count=" + Counter.count);
}
}
نتيجة التشغيل: Counter.count=992
نتيجة التشغيل لا تزال ليست 1000 كما توقعنا، دعنا نحلل الأسباب أدناه.
في المقالة Java Garbage Collection، تم وصف تخصيص الذاكرة أثناء تشغيل JVM. إحدى مناطق الذاكرة هي مكدس الجهاز الظاهري jvm. يحتوي كل مؤشر ترابط على مكدس خيط عند تشغيله. يقوم مكدس الخيط بتخزين معلومات القيمة المتغيرة عند تشغيل الخيط. عندما يصل مؤشر ترابط إلى قيمة كائن، فإنه يعثر أولاً على قيمة المتغير المقابل لذاكرة الكومة من خلال مرجع الكائن، ثم يقوم بتحميل القيمة المحددة لمتغير ذاكرة الكومة في الذاكرة المحلية لمؤشر الترابط لإنشاء نسخة من بعد ذلك، لا علاقة للخيط بقيمة متغير الكائن في ذاكرة الكومة، بل يقوم بتعديل قيمة متغير النسخ مباشرة في لحظة معينة بعد التعديل (قبل خروج الخيط). تتم إعادة كتابة قيمة نسخة متغير مؤشر الترابط تلقائيًا إلى متغير الكائن في الكومة. بهذه الطريقة، تتغير قيمة الكائن الموجود في الكومة. الصورة أدناه
وصف هذا التفاعل
قراءة وتحميل نسخ المتغيرات من الذاكرة الرئيسية إلى الذاكرة العاملة الحالية
استخدام وتعيين تنفيذ التعليمات البرمجية وتغيير قيم المتغيرات المشتركة
تخزين وكتابة تحديث المحتوى المتعلق بالذاكرة الرئيسية باستخدام بيانات الذاكرة العاملة
حيث يمكن أن يظهر الاستخدام والتخصيص عدة مرات
ومع ذلك، هذه العمليات ليست ذرية، أي أنه بعد تحميل القراءة، إذا تم تعديل متغير عدد الذاكرة الرئيسية، فلن تتغير القيمة في الذاكرة العاملة لمؤشر الترابط لأنه تم تحميلها، وبالتالي فإن النتيجة المحسوبة ستكون كما هو متوقع نفس الشيء
بالنسبة للمتغيرات المعدلة المتطايرة، يضمن الجهاز الظاهري JVM فقط أن القيمة المحملة من الذاكرة الرئيسية إلى الذاكرة العاملة للخيط هي الأحدث
على سبيل المثال، إذا وجد الخيط 1 والخيط 2 أن قيمة العد في الذاكرة الرئيسية هي 5 أثناء عمليات القراءة والتحميل، فسيتم تحميل أحدث قيمة.
بعد تعديل عدد الكومة للخيط 1، سيتم كتابته في الذاكرة الرئيسية، وسيصبح متغير العدد في الذاكرة الرئيسية 6.
نظرًا لأن مؤشر الترابط 2 قد أجرى بالفعل عمليات القراءة والتحميل، فإنه بعد إجراء العملية، سيقوم أيضًا بتحديث قيمة متغير عدد الذاكرة الرئيسية إلى 6.
ونتيجة لذلك، بعد تعديل خيطين في الوقت المناسب باستخدام الكلمة الأساسية المتقلبة، سيظل هناك التزامن.