CodeGym /مدونة جافا /Random-AR /إدارة المواضيع. الكلمة الأساسية المتقلبة وطريقة العائد ()...
John Squirrels
مستوى
San Francisco

إدارة المواضيع. الكلمة الأساسية المتقلبة وطريقة العائد ().

نشرت في المجموعة
أهلاً! نواصل دراستنا للتعددية. اليوم سنتعرف على الكلمة volatileالأساسية والطريقة yield(). هيا بنا نتعمق :)

الكلمة الأساسية المتقلبة

عند إنشاء تطبيقات متعددة الخيوط، يمكن أن نواجه مشكلتين خطيرتين. أولاً، عند تشغيل تطبيق متعدد الخيوط، يمكن لخيوط مختلفة تخزين قيم المتغيرات مؤقتًا (تحدثنا عن هذا بالفعل في الدرس المعنون "استخدام المتغير" ). يمكن أن يكون لديك موقف يقوم فيه أحد الخيوط بتغيير قيمة المتغير، لكن الخيط الثاني لا يرى التغيير، لأنه يعمل مع نسخته المخزنة مؤقتًا من المتغير. وبطبيعة الحال، يمكن أن تكون العواقب خطيرة. لنفترض أنه ليس مجرد متغير قديم، بل رصيد حسابك المصرفي، والذي يبدأ فجأة بالقفز بشكل عشوائي لأعلى ولأسفل :) لا يبدو هذا ممتعًا، أليس كذلك؟ ثانيًا، في Java، تكون عمليات القراءة والكتابة لجميع الأنواع البدائية، باستثناء longو double، عمليات ذرية. حسناً، على سبيل المثال، إذا قمت بتغيير قيمة متغير intفي موضوع ما، وفي موضوع آخر قرأت قيمة المتغير، فإما ستحصل على قيمته القديمة أو القيمة الجديدة، أي القيمة التي نتجت عن التغيير في الموضوع 1. لا توجد "قيم متوسطة". ومع ذلك، هذا لا يعمل مع longs و doubles. لماذا؟ بسبب الدعم عبر الأنظمة الأساسية. هل تتذكر أننا قلنا في مستويات البداية أن المبدأ التوجيهي لجافا هو "الكتابة مرة واحدة، والتشغيل في أي مكان"؟ وهذا يعني الدعم عبر الأنظمة الأساسية. بمعنى آخر، يعمل تطبيق Java على جميع أنواع الأنظمة الأساسية المختلفة. على سبيل المثال، في أنظمة تشغيل Windows، إصدارات مختلفة من Linux أو MacOS. وسوف تعمل دون وجود عوائق على كل منهم. يبلغ وزنها 64 بت، longوهي doubleأثقل البدائيات في جافا. وبعض الأنظمة الأساسية 32 بت لا تنفذ ببساطة القراءة والكتابة الذرية لمتغيرات 64 بت. تتم قراءة هذه المتغيرات وكتابتها في عمليتين. أولاً، تتم كتابة أول 32 بت للمتغير، ثم تتم كتابة 32 بت أخرى. ونتيجة لذلك، قد تنشأ مشكلة. يكتب أحد الخيوط قيمة 64 بت إلى Xمتغير ويقوم بذلك في عمليتين. في الوقت نفسه، يحاول الخيط الثاني قراءة قيمة المتغير ويفعل ذلك بين هاتين العمليتين - عندما تتم كتابة أول 32 بت، لكن الـ 32 بت الثانية لم تتم كتابتها. ونتيجة لذلك، فإنه يقرأ قيمة متوسطة وغير صحيحة، ويكون لدينا خطأ. على سبيل المثال، إذا حاولنا على مثل هذه المنصة كتابة الرقم إلى 9223372036854775809 في متغير، فسوف يشغل 64 بت. في النموذج الثنائي، يبدو كما يلي: 10000000000000000000000000000000000000000000000001 يبدأ الخيط الأول في كتابة الرقم للمتغير. في البداية، يكتب أول 32 بت (100000000000000000000000) ثم الـ 32 بت الثانية (000000000000000000000001). ويمكن ربط الخيط الثاني بين هذه العمليات، وقراءة القيمة المتوسطة للمتغير (1000000000000000000000000)، وهي أول 32 بت التي تمت كتابتها بالفعل. في النظام العشري، هذا الرقم هو 2,147,483,648. بمعنى آخر، أردنا فقط كتابة الرقم 9223372036854775809 إلى متغير، ولكن نظرًا لأن هذه العملية ليست ذرية على بعض المنصات، فلدينا الرقم الشرير 2,147,483,648، الذي خرج من العدم وسيكون له تأثير غير معروف برنامج. يقرأ الخيط الثاني ببساطة قيمة المتغير قبل الانتهاء من كتابته، أي أن الخيط رأى أول 32 بت، ولكن ليس الـ 32 بت الثانية. وبطبيعة الحال، لم تنشأ هذه المشاكل بالأمس. تحلها Java بكلمة رئيسية واحدة: volatile. إذا استخدمنا volatileالكلمة الأساسية عند الإعلان عن بعض المتغيرات في برنامجنا ...
public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…هذا يعني انه:
  1. سيتم قراءتها وكتابتها دائمًا بشكل ذري. حتى لو كان 64 بت doubleأو long.
  2. لن يقوم جهاز Java بتخزينه مؤقتًا. لذلك لن يكون لديك موقف حيث تعمل 10 سلاسل رسائل مع نسخها المحلية الخاصة.
وهكذا يتم حل مشكلتين خطيرتين للغاية بكلمة واحدة فقط :)

طريقة العائد ().

لقد قمنا بالفعل بمراجعة العديد من Threadأساليب الفصل الدراسي، ولكن هناك طريقة مهمة ستكون جديدة عليك. إنها yield()الطريقة . ويفعل بالضبط ما يوحي به اسمه! إدارة المواضيع.  الكلمة الأساسية المتقلبة وطريقة العائد () - 2عندما نستدعي yieldالطريقة على سلسلة رسائل، فإنها تتحدث في الواقع إلى الخيوط الأخرى: "مرحبًا يا شباب." أنا لست في عجلة من أمري للذهاب إلى أي مكان، لذا إذا كان من المهم لأي منكم أن يحصل على وقت المعالج، فليأخذه - يمكنني الانتظار". فيما يلي مثال بسيط لكيفية عمل ذلك:
public class ThreadExample extends Thread {

   public ThreadExample() {
       this.start();
   }

   public void run() {

       System.out.println(Thread.currentThread().getName() + " yields its place to others");
       Thread.yield();
       System.out.println(Thread.currentThread().getName() + " has finished executing.");
   }

   public static void main(String[] args) {
       new ThreadExample();
       new ThreadExample();
       new ThreadExample();
   }
}
نقوم بإنشاء وبدء ثلاثة سلاسل بالتسلسل: Thread-0و Thread-1و و Thread-2. Thread-0يبدأ أولاً ويستسلم فوراً للآخرين. ثم Thread-1يتم البدء وينتج أيضًا. ثم Thread-2يتم البدء، والذي ينتج أيضًا. ليس لدينا أي سلاسل رسائل أخرى، وبعد أن Thread-2حصل على مكانه الأخير، يقول برنامج جدولة سلاسل الرسائل، "حسنًا، ليس هناك المزيد من سلاسل الرسائل الجديدة." من لدينا في قائمة الانتظار؟ ومن الذي أخل بمكانته من قبل Thread-2؟ ويبدو أنه كان Thread-1. حسنًا، هذا يعني أننا سنتركه يعمل. Thread-1يكمل عمله ثم يواصل برنامج جدولة الخيط تنسيقه: "حسنًا، Thread-1انتهيت." هل لدينا أي شخص آخر في قائمة الانتظار؟ Thread-0 موجود في قائمة الانتظار: لقد حصل على مكانه قبل Thread-1. والآن يحصل على دوره ويستمر حتى الاكتمال. ثم ينتهي المجدول من تنسيق سلاسل الرسائل: "حسنًا، Thread-2لقد استسلمت لسلاسل الرسائل الأخرى، وقد تم الانتهاء منها جميعًا الآن." لقد كنت آخر من استسلم، والآن حان دورك. ثم Thread-2يركض حتى الانتهاء. سيبدو إخراج وحدة التحكم كما يلي: يعطي Thread-0 مكانه للآخرين، يعطي Thread-1 مكانه للآخرين، يعطي Thread-2 مكانه للآخرين. انتهى تنفيذ Thread-1. انتهى تنفيذ مؤشر الترابط-0. تم الانتهاء من تنفيذ مؤشر الترابط 2. بالطبع، قد يبدأ برنامج جدولة سلاسل الرسائل بترتيب مختلف (على سبيل المثال، 2-1-0 بدلاً من 0-1-2)، لكن المبدأ يظل كما هو.

يحدث قبل القواعد

آخر شيء سنتطرق إليه اليوم هو مفهوم " يحدث من قبل ". كما تعلم بالفعل، في Java، يقوم برنامج جدولة سلاسل الرسائل بتنفيذ الجزء الأكبر من العمل المتضمن في تخصيص الوقت والموارد لسلاسل الرسائل لأداء مهامها. لقد رأيت أيضًا مرارًا وتكرارًا كيف يتم تنفيذ سلاسل الرسائل بترتيب عشوائي يكون من المستحيل عادةً التنبؤ به. وبشكل عام، بعد البرمجة "المتسلسلة" التي قمنا بها سابقًا، تبدو البرمجة متعددة الخيوط وكأنها شيء عشوائي. لقد أصبحت تعتقد بالفعل أنه يمكنك استخدام مجموعة من الأساليب للتحكم في تدفق برنامج متعدد مؤشرات الترابط. لكن تعدد مؤشرات الترابط في Java له ركيزة أخرى - القواعد الأربعة " يحدث قبل ". فهم هذه القواعد بسيط للغاية. تخيل أن لدينا خيطين - Aو B. يمكن لكل من هذه الخيوط تنفيذ العمليات 1و 2. في كل قاعدة، عندما نقول ' A يحدث قبل B '، نعني أن جميع التغييرات التي أجراها الخيط Aقبل العملية 1والتغييرات الناتجة عن هذه العملية تكون مرئية للخيط Bعند 2تنفيذ العملية وبعدها. تضمن كل قاعدة أنه عند كتابة برنامج متعدد الخيوط، ستحدث أحداث معينة قبل الأحداث الأخرى بنسبة 100٪ من الوقت، وأنه في وقت التشغيل، سيكون 2مؤشر الترابط Bدائمًا على علم بالتغييرات التي Aأجراها الخيط أثناء التشغيل 1. دعونا نراجعها.

المادة 1.

يحدث تحرير كائن المزامنة (mutex) قبل أن يتم الحصول على نفس الشاشة بواسطة مؤشر ترابط آخر. أعتقد أنك تفهم كل شيء هنا. إذا تم الحصول على كائن المزامنة لكائن أو فئة بواسطة مؤشر ترابط واحد.، على سبيل المثال، بواسطة مؤشر ترابط A، فلا يمكن لمؤشر ترابط آخر (مؤشر ترابط B) الحصول عليه في نفس الوقت. يجب أن ينتظر حتى يتم تحرير كائن المزامنة (mutex).

القاعدة 2.

الطريقة تحدث منThread.start() قبل . مرة أخرى، لا يوجد شيء صعب هنا. أنت تعلم بالفعل أنه لبدء تشغيل التعليمات البرمجية داخل الطريقة، يجب عليك استدعاء الطريقة في سلسلة المحادثات. على وجه التحديد، طريقة البداية، وليس الطريقة نفسها! تضمن هذه القاعدة أن قيم جميع المتغيرات التي تم تعيينها قبل الاستدعاء ستكون مرئية داخل الطريقة بمجرد البدء. Thread.run()run()start()run()Thread.start()run()

القاعدة 3.

نهاية الطريقة run()تحدث قبل العودة من join()الطريقة. لنعد إلى موضوعينا: Aو B. نحن نسمي join()الطريقة بحيث Bيتم ضمان انتظار الخيط حتى اكتمال الخيط Aقبل أن يقوم بعمله. هذا يعني أن طريقة الكائن A run()مضمونة للتشغيل حتى النهاية. وجميع التغييرات التي تطرأ على البيانات التي تحدث في run()طريقة الخيط Aمضمونة بنسبة مائة بالمائة أن تكون مرئية في الخيط Bبمجرد الانتهاء منها في انتظار Aانتهاء الخيط من عمله حتى يتمكن من بدء عمله الخاص.

القاعدة 4.

تتم الكتابة إلى volatileمتغير قبل القراءة من نفس المتغير. عندما نستخدم الكلمة volatileالأساسية، نحصل دائمًا على القيمة الحالية. حتى مع وجود longأو double(تحدثنا سابقًا عن المشكلات التي يمكن أن تحدث هنا). كما تعلم بالفعل، فإن التغييرات التي يتم إجراؤها على بعض سلاسل الرسائل لا تكون مرئية دائمًا لسلاسل الرسائل الأخرى. ولكن، بالطبع، هناك مواقف متكررة للغاية حيث لا يناسبنا هذا السلوك. لنفترض أننا قمنا بتعيين قيمة لمتغير في مؤشر الترابط A:
int z;.

z = 555;
إذا كان Bمن المفترض أن يعرض مؤشر الترابط الخاص بنا قيمة المتغير zعلى وحدة التحكم، فيمكنه بسهولة عرض 0، لأنه لا يعرف القيمة المخصصة. لكن القاعدة 4 تضمن أنه إذا أعلنا عن zالمتغير كـ volatile، فإن التغييرات التي تطرأ على قيمته في مؤشر ترابط واحد ستكون دائمًا مرئية في مؤشر ترابط آخر. لو أضفنا الكلمة volatileإلى الكود السابق...
volatile int z;.

z = 555;
...ثم نمنع الموقف الذي Bقد يعرض فيه مؤشر الترابط 0. volatileتتم الكتابة إلى المتغيرات قبل القراءة منها.
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION