تصف العديد من المواد عبر الإنترنت نموذج ذاكرة Java ، والذي سيقدم أن هناك ذاكرة رئيسية ، وأن كل مؤشر ترابط عامل لديه ذاكرة العمل الخاصة به. سيكون هناك جزء واحد من البيانات في الذاكرة الرئيسية وقطعة واحدة في الذاكرة العاملة. سيكون هناك العديد من العمليات الذرية بين الذاكرة العاملة والذاكرة الرئيسية للمزامنة.
الصورة التالية من هذه المدونة
ومع ذلك ، نظرًا للتطور المستمر لإصدار Java ، فقد تغير نموذج الذاكرة أيضًا. تتحدث هذه المقالة فقط عن بعض ميزات نموذج ذاكرة Java. سواء كان نموذج ذاكرة جديد أو نموذج ذاكرة قديم ، سيبدو أكثر وضوحًا بعد فهم هذه الميزات.
1
الذرة تعني أن العملية غير متوقعة. حتى عند تنفيذ خيوط متعددة معًا ، بمجرد بدء تشغيل ما ، لن يتم إزعاجها بواسطة مؤشرات الترابط الأخرى.
يُعتقد عمومًا أن تعليمات وحدة المعالجة المركزية هي العمليات الذرية ، لكن الرمز الذي نكتبه ليس بالضرورة عمليات ذرية.
على سبيل المثال ، i ++. هذه العملية ليست عملية ذرية ، وهي مقسمة بشكل أساسي إلى 3 عمليات ، وقراءة i ، وأداء +1 ، وتعيين قيمة لـ i.
لنفترض أن هناك خيطان. عندما يقرأ الخيط الأول i = 1 ، لم يتم تنفيذ عملية +1 بعد ، والتبديل إلى الخيط الثاني. في هذا الوقت ، يقرأ الخيط الثاني أيضًا i = 1. ثم يقوم الخيطان بإجراء عمليات +1 لاحقة ثم تعيين القيم مرة أخرى ، أنا لست 3 ، ولكن 2. من الواضح أن هناك تناقض في البيانات.
على سبيل المثال ، فإن قراءة قيمة طويلة 64 بت على JVM 32 بت ليست عملية ذرية. بالطبع ، يقرأ 32 بت JVM أعداد صحيحة 32 بت باعتبارها عملية ذرية.
2. الترتيب
أثناء التزامن ، قد يكون تنفيذ البرنامج خارج الترتيب.
عندما يقوم الكمبيوتر بتنفيذ رمز ، لا يتم تنفيذه بالضرورة بترتيب البرنامج.
class orderexample {int a = 0 ؛ العلم المنطقي = خطأ ؛ كاتب باطل عام () {a = 1 ؛ العلم = صحيح ؛ } public void reader () {if (flag) {int i = a +1 ؛ }}} على سبيل المثال ، في الكود أعلاه ، يتم استدعاء طريقتين من قبل مؤشر ترابط على التوالي. وفقًا للفطرة السليمة ، يجب أن يقوم مؤشر ترابط الكتابة أولاً بتنفيذ A = 1 ، ثم تنفيذ Flag = True. عندما يقرأ موضوع القراءة ، i = 2 ؛
ولكن لأن A = 1 و Flag = True ، لا يوجد ارتباط منطقي. لذلك ، من الممكن عكس ترتيب التنفيذ ، ومن الممكن تنفيذ Flag = true أولاً ، ثم A = 1. في هذا الوقت ، عندما يكون العلم = صحيح ، قم بالتبديل إلى مؤشر ترابط القراءة. في هذا الوقت ، لم يتم تنفيذ A = 1 بعد ، ثم سيكون مؤشر ترابط القراءة I = 1.
بالطبع هذا ليس مطلقًا. من الممكن أن يكون هناك خارج الترتيب وقد لا يحدث.
فلماذا هناك خارج الترتيب؟ هذا يبدأ بتعليمات وحدة المعالجة المركزية. بعد تجميع الكود في Java ، يتم تحويله أخيرًا إلى رمز التجميع.
يمكن تقسيم تنفيذ التعليمات إلى العديد من الخطوات. على افتراض أن تعليمات وحدة المعالجة المركزية مقسمة إلى الخطوات التالية
افترض أن هناك تعليمين هنا
بشكل عام ، سوف نعتقد أن الإرشادات يتم تنفيذها بشكل تسلسلي ، وتنفيذ التعليمات أولاً 1 ، ثم تنفيذ التعليمات 2. على افتراض أن كل خطوة تتطلب فترة زمنية واحدة لوحدة المعالجة المركزية ، فإن تنفيذ هاتين التعليمات يتطلب 10 فترات زمنية لوحدة المعالجة المركزية ، وهو أمر غير فعال للغاية للقيام بذلك. في الواقع ، يتم تنفيذ التعليمات بالتوازي. بالطبع ، عندما تنفذ التعليمات الأولى إذا ، لا يمكن أن تنفذ التعليمات الثانية إذا كانت تسجيلات التعليمات وما شابه لا يمكن احتلالها في نفس الوقت. لذلك كما هو موضح في الشكل أعلاه ، يتم تنفيذ التعليمتين بالتوازي بطريقة متداخلة نسبيا. عندما تقوم التعليمات 1 بتنفيذ المعرف ، تقوم التعليمات 2 بتنفيذ IF. وبهذه الطريقة ، تم تنفيذ تعليمين في 6 فترات زمنية لوحدة المعالجة المركزية فقط ، والتي كانت فعالة نسبيا.
وفقًا لهذه الفكرة ، دعونا نلقي نظرة على كيفية تنفيذ تعليمات A = B+C.
كما هو موضح في الشكل ، هناك عملية خاملة (x) أثناء عملية إضافة ، لأنه عندما تريد إضافة B و C ، عندما تكون عملية Add's X في الشكل ، لم تقرأ C من الذاكرة (C فقط قراءة من الذاكرة عند اكتمال عملية MEM. لذلك ليست هناك حاجة إلى انتظار تنفيذ WB قبل إجراء الإضافة). لذلك ، سيكون هناك وقت خامل (x) في عملية إضافة. في عملية SW ، نظرًا لأنه لا يمكن تنفيذ تعليمات EX في وقت واحد مع تعليمات Add Ex ، سيكون هناك وقت خامل (x).
بعد ذلك ، دعونا نعطي مثالًا أكثر تعقيدًا قليلاً
أ = ب+ج
D = EF
التعليمات المقابلة هي كما يلي
والسبب مشابه لما ورد أعلاه ، لذلك لن أقوم بتحليله هنا. لقد وجدنا أن هناك الكثير من X هنا ، وهناك الكثير من دورات الوقت الضائعة ، ويتأثر الأداء أيضًا. هل هناك طريقة لتقليل عدد XS؟
نأمل أن نستخدم بعض العمليات لملء وقت فراغ X ، لأن ADD لها اعتماد على البيانات مع الإرشادات المذكورة أعلاه ، ونأمل أن نستخدم بعض الإرشادات دون اعتماد البيانات لملء وقت الفراغ الناتج عن الاعتماد على البيانات.
قمنا بتغيير ترتيب التعليمات
بعد تغيير ترتيب التعليمات ، يتم القضاء على x. انخفضت الفترة الزمنية للتشغيل الكلي أيضا.
إعادة ترتيب التعليمات يمكن أن تجعل خط الأنابيب أكثر سلاسة
بطبيعة الحال ، فإن مبدأ إعادة ترتيب التعليمات هو أنه لا يمكن تدمير دلالات البرنامج التسلسلي. على سبيل المثال ، a = 1 ، b = a+1 ، لن يتم إعادة ترتيب هذه التعليمات لأن النتيجة التسلسلية لإعادة الترتيب تختلف عن الإلغاء الأصلي.
إن إعادة ترتيب التعليمات هي مجرد وسيلة لتحسين برنامج التحويل البرمجي أو وحدة المعالجة المركزية ، وقد تسبب هذا التحسين في مشاكل في البرنامج في بداية هذا الفصل.
كيف تحلها؟ استخدم الكلمة الرئيسية المتطايرة ، وسيتم تقديم هذه السلسلة اللاحقة.
3. الرؤية
تشير الرؤية إلى ما إذا كان بإمكان مؤشرات الترابط الأخرى على الفور معرفة التعديل عندما يقوم مؤشر ترابط بتعديل قيمة المتغير المشترك.
قد تنشأ قضايا الرؤية في روابط مختلفة. على سبيل المثال ، سيؤدي إعادة ترتيب التعليمات المذكورة للتو أيضًا إلى حدوث مشاكل في الرؤية ، وبالإضافة إلى ذلك ، فإن تحسين برنامج التحويل البرمجي أو تحسين بعض الأجهزة سيؤدي أيضًا إلى مشاكل في الرؤية.
على سبيل المثال ، يقوم مؤشر ترابط بتحسين قيمة مشتركة في الذاكرة ، بينما يقوم مؤشر ترابط آخر بتحسين القيمة المشتركة في ذاكرة التخزين المؤقت. عند تعديل القيمة في الذاكرة ، لا تعرف القيمة المخزنة مؤقتًا التعديل.
على سبيل المثال ، بعض تحسينات الأجهزة ، عندما يكتب البرنامج عدة مرات إلى نفس العنوان ، فإنه يعتقد أنه غير ضروري ولا يحتفظ بالكتابة الأخيرة فقط ، وبالتالي فإن البيانات المكتوبة من قبل ستكون غير مرئية في مؤشرات الترابط الأخرى.
باختصار ، تنبع معظم المشكلات مع الرؤية من التحسين.
بعد ذلك ، دعونا نلقي نظرة على مشكلة رؤية ناشئة عن مستوى الجهاز الافتراضي Java
المشكلة تأتي من مدونة
حزمة edu.hushi.jvm ؛ /** * * Author -10 * */Class Public Classibilitytest يمتد Thread {Private Boolean Stop ؛ public void run () {int i = 0 ؛ بينما (! توقف) {i ++ ؛ } system.out.println ("Finish loop ، i =" + i) ؛ } public void stopit () {stop = true ؛ } public boolean getStop () {return stop ؛ } public static void main (string [] args) يلقي الاستثناء {prosibilitytest v = new prosibilityTest () ؛ v.start () ؛ thread.sleep (1000) ؛ v.stopit () ؛ thread.sleep (2000) ؛ System.out.println ("Finish Main") ؛ system.out.println (v.getStop ()) ؛ }} الكود بسيط للغاية. يحتفظ مؤشر ترابط V i ++ في الحلقة حتى يتصل مؤشر الترابط الرئيسي بطريقة الإيقاف ، وتغيير قيمة متغير STOP في مؤشر الترابط V لإيقاف الحلقة.
تحدث المشكلات عندما يتم تشغيل التعليمات البرمجية البسيطة على ما يبدو. يمكن أن يمنع هذا البرنامج مؤشرات الترابط من إجراء عمليات التمييز الذاتي في وضع العميل ، ولكن في وضع الخادم ، ستكون حلقة لا حصر لها أولاً. (المزيد من تحسين JVM في وضع الخادم)
معظم الأنظمة 64 بت هي وضع الخادم ، ويتم تشغيله في وضع الخادم:
إنهاء رئيسي
حقيقي
سيتم طباعة هاتين الجملتين فقط ، ولكن لن تتم طباعة حلقة النهاية. ولكن يمكنك أن تجد أن قيمة التوقف صحيحة بالفعل.
يستخدم مؤلف هذه المدونة أدوات لاستعادة البرنامج إلى رمز التجميع
يتم اعتراض جزء فقط من رمز التجميع هنا ، والجزء الأحمر هو جزء الحلقة. يمكن أن نرى بوضوح أن 0x0193bf9d هو مجرد التحقق من التوقف ، في حين أن الجزء الأحمر لا يأخذ قيمة التوقف ، لذلك يتم تنفيذ حلقة لا حصر لها.
هذا هو نتيجة تحسين JVM. كيف تتجنب ذلك؟ مثل إعادة ترتيب التوجيه ، استخدم الكلمة الرئيسية المتطايرة.
إذا تمت إضافة متقلبة ، فقم بإعادةه إلى رمز التجميع وستجد أن كل حلقة ستحصل على قيمة التوقف.
بعد ذلك ، دعونا نلقي نظرة على بعض الأمثلة في "مواصفات لغة جافا"
يوضح الشكل أعلاه أن إعادة ترتيب التعليمات ستؤدي إلى نتائج مختلفة.
السبب في أن R5 = R2 في الشكل أعلاه هو أن r2 = r1.x ، r5 = r1.x ، ويتم تحسينه مباشرة إلى r5 = r2 في وقت الترجمة. في النهاية ، النتائج مختلفة.
4
5. مفهوم سلامة الموضوع
إنه يشير إلى حقيقة أنه عندما يتم استدعاء مكتبة وظيفة معينة أو وظائف في بيئة متعددة الخيوط ، يمكنها معالجة المتغيرات المحلية لكل مؤشر ترابط بشكل صحيح وتمكين وظائف البرنامج من الانتهاء بشكل صحيح.
على سبيل المثال ، مثال I ++ المذكور في البداية
هذا سوف يؤدي إلى غير آمن الموضوع.
للحصول على تفاصيل حول سلامة الموضوع ، يرجى الرجوع إلى هذه المدونة التي كتبتها من قبل ، أو اتبع السلسلة اللاحقة ، وسوف تتحدث أيضًا عن المحتوى ذي الصلة.