JavaRush /مدونة جافا /Random-AR /تعدد مؤشرات الترابط في جافا: الجوهر والمزايا والمزالق الم...

تعدد مؤشرات الترابط في جافا: الجوهر والمزايا والمزالق المشتركة

نشرت في المجموعة
مرحبًا! بداية، تهانينا: لقد وصلت إلى موضوع تعدد مؤشرات الترابط في Java! وهذا إنجاز جدي، ولا يزال أمامنا طريق طويل لنقطعه. لكن استعد: هذا من أصعب المواضيع في الدورة. والنقطة ليست أنه يتم استخدام فئات معقدة أو العديد من الأساليب هنا: على العكس من ذلك، لا يوجد حتى عشرين. الأمر الأكثر أنك بحاجة إلى تغيير تفكيرك قليلاً. في السابق، تم تنفيذ برامجك بشكل تسلسلي. اتبعت بعض أسطر التعليمات البرمجية بعضها البعض، وتبعت بعض الأساليب البعض الآخر، وكان كل شيء واضحًا بشكل عام. أولاً، قم بحساب شيء ما، ثم اعرض النتيجة على وحدة التحكم، ثم قم بإنهاء البرنامج. لفهم تعدد مؤشرات الترابط، من الأفضل التفكير في التزامن. لنبدأ بشيء بسيط جدًا :) تعدد مؤشرات الترابط في Java: الجوهر والمزايا والمزالق الشائعة - 1تخيل أن عائلتك تنتقل من منزل إلى آخر. جزء مهم من النقل هو تعبئة كتبك. لقد جمعت الكثير من الكتب، وتحتاج إلى وضعها في الصناديق. الآن أنت فقط حر. تقوم أمي بإعداد الطعام، والأخ يجمع الملابس، وقد ذهبت الأخت إلى المتجر. يمكنك إدارة الأمر بمفردك، على الأقل، وعاجلاً أم آجلاً، ستكمل المهمة بنفسك، لكن الأمر سيستغرق الكثير من الوقت. ومع ذلك، في غضون 20 دقيقة ستعود أختك من المتجر، وليس لديها ما تفعله. حتى تتمكن من الانضمام إليك. ظلت المهمة كما هي: وضع الكتب في الصناديق. إنه يعمل بسرعة مضاعفة فقط. لماذا؟ لأن العمل يتم بالتوازي. يقوم "خيطان" مختلفان (أنت وأختك) بتنفيذ نفس المهمة في نفس الوقت، وإذا لم يتغير شيء، فسيكون الفارق الزمني كبيرًا جدًا مقارنة بالموقف الذي ستفعل فيه كل شيء بمفردك. إذا أكمل أخوك مهمته قريبًا، فيمكنه مساعدتك، وستسير الأمور بشكل أسرع.

المشاكل التي يحلها تعدد العمليات في Java

في الأساس، تم اختراع Java multithreading لحل مشكلتين رئيسيتين:
  1. تنفيذ إجراءات متعددة في نفس الوقت.

    في المثال أعلاه، قامت خيوط مختلفة (أي أفراد الأسرة) بتنفيذ عدة إجراءات بالتوازي: غسل الأطباق، والذهاب إلى المتجر، وطي الأشياء.

    يمكن إعطاء مثال أكثر "مبرمج". تخيل أن لديك برنامجًا بواجهة مستخدم. عند الضغط على زر متابعة، يجب أن تتم بعض العمليات الحسابية داخل البرنامج، ويجب أن يرى المستخدم شاشة الواجهة التالية. إذا تم تنفيذ هذه الإجراءات بالتتابع، بعد النقر فوق الزر "متابعة"، فسيتم تجميد البرنامج ببساطة. وسيظهر للمستخدم نفس الشاشة مع زر "متابعة" حتى تتم جميع الحسابات الداخلية ويصل البرنامج إلى الجزء الذي سيبدأ فيه رسم الواجهة.

    حسنا، دعونا ننتظر بضع دقائق!

    تعدد مؤشرات الترابط في Java: الجوهر والمزايا والمزالق الشائعة - 3

    يمكننا أيضًا إعادة صياغة برنامجنا، أو، كما يقول المبرمجون، "الموازاة". اسمح بإجراء الحسابات اللازمة في موضوع واحد، وعرض الواجهة في موضوع آخر. معظم أجهزة الكمبيوتر لديها موارد كافية لهذا الغرض. وفي هذه الحالة لن يكون البرنامج “غبيا”، وسيتنقل المستخدم بهدوء بين شاشات الواجهة دون القلق بشأن ما يحدث بداخله. لا يتدخل :)

  2. تسريع العمليات الحسابية.

    كل شيء أبسط بكثير هنا. إذا كان معالجنا يحتوي على عدة مراكز، وكانت معظم المعالجات الآن متعددة النواة، فيمكن حل قائمة المهام لدينا بالتوازي بواسطة عدة مراكز. من الواضح أننا إذا أردنا حل 1000 مشكلة وتم حل كل واحدة منها في ثانية واحدة، فسوف تتعامل نواة واحدة مع القائمة في 1000 ثانية، ونواتان في 500 ثانية، وثلاثة في ما يزيد قليلاً عن 333 ثانية، وهكذا.

ولكن، كما قرأت بالفعل في المحاضرة، فإن الأنظمة الحديثة ذكية للغاية، وحتى في أحد نواة الحوسبة، فهي قادرة على تنفيذ التوازي، أو التوازي الزائف، عندما يتم تنفيذ المهام بالتناوب. دعنا ننتقل من الأشياء العامة إلى أشياء محددة ونتعرف على الفئة الرئيسية في مكتبة Java المتعلقة بتعدد مؤشرات الترابط - java.lang.Thread. بالمعنى الدقيق للكلمة، يتم تمثيل المواضيع في Java بمثيلات الفئة Thread. أي أنه لإنشاء 10 سلاسل وتشغيلها، ستحتاج إلى 10 كائنات من هذه الفئة. لنكتب أبسط مثال:
public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("I'm Thread! My name is " + getName());
   }
}
لإنشاء سلاسل رسائل وتشغيلها، نحتاج إلى إنشاء فئة ووراثتها من ملف java.lang. Threadوتجاوز الطريقة فيه run(). هذا الأخير مهم جدا. في الطريقة التي run()نصف بها المنطق الذي يجب أن ينفذه مؤشر الترابط الخاص بنا. الآن، إذا أنشأنا مثيلًا MyFirstThreadوقمنا بتشغيله، run()فستطبع الطريقة سطرًا باسمها على وحدة التحكم: getName()تطبع الطريقة اسم "النظام" للخيط، والذي يتم تعيينه تلقائيًا. على الرغم من أنه في الواقع لماذا "إذا"؟ دعونا ننشئ ونختبر!
public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {

           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
إخراج وحدة التحكم: أنا خيط! اسمي الموضوع-2 أنا الموضوع! اسمي الموضوع-1 أنا الموضوع! اسمي الموضوع-0 أنا الموضوع! اسمي الموضوع-3 أنا الموضوع! اسمي الموضوع-6 أنا الموضوع! اسمي الموضوع-7 أنا الموضوع! اسمي Thread-4 أنا خيط! اسمي الموضوع-5 أنا الموضوع! اسمي الموضوع-9 أنا الموضوع! اسمي Thread-8 نقوم بإنشاء 10 سلاسل (كائنات) MyFirstThreadترث منها Threadوتشغيلها عن طريق استدعاء طريقة الكائن start(). بعد استدعاء الطريقة ، start()تبدأ طريقتها في العمل run()، ويتم تنفيذ المنطق المكتوب فيها. ملاحظة: أسماء المواضيع ليست بالترتيب. إنه أمر غريب جدًا، لماذا لم يتم إعدامهم بدورهم: Thread-0, Thread-1, Thread-2وهكذا؟ هذا هو بالضبط مثال على الحالات التي لن ينجح فيها التفكير القياسي "المتسلسل". الحقيقة هي أننا في هذه الحالة نصدر فقط أوامر لإنشاء وإطلاق 10 سلاسل رسائل. يتم تحديد الترتيب الذي سيتم إطلاقها به بواسطة برنامج جدولة الخيط: آلية خاصة داخل نظام التشغيل. إن كيفية تنظيمها بالضبط وعلى أي مبدأ تتخذ القرارات هو موضوع معقد للغاية، ولن نتعمق فيه الآن. الشيء الرئيسي الذي يجب تذكره هو أن المبرمج لا يمكنه التحكم في تسلسل تنفيذ الخيط. لإدراك خطورة الموقف، حاول تشغيل الطريقة main()من المثال أعلاه عدة مرات. إخراج وحدة التحكم الثانية: أنا خيط! اسمي الموضوع-0 أنا الموضوع! اسمي Thread-4 أنا خيط! اسمي الموضوع-3 أنا الموضوع! اسمي الموضوع-2 أنا الموضوع! اسمي الموضوع-1 أنا الموضوع! اسمي الموضوع-5 أنا الموضوع! اسمي الموضوع-6 أنا الموضوع! اسمي Thread-8 أنا خيط! اسمي الموضوع-9 أنا الموضوع! اسمي Thread-7 إخراج وحدة التحكم الثالثة: أنا Thread! اسمي الموضوع-0 أنا الموضوع! اسمي الموضوع-3 أنا الموضوع! اسمي الموضوع-1 أنا الموضوع! اسمي الموضوع-2 أنا الموضوع! اسمي الموضوع-6 أنا الموضوع! اسمي Thread-4 أنا خيط! اسمي الموضوع-9 أنا الموضوع! اسمي الموضوع-5 أنا الموضوع! اسمي الموضوع-7 أنا الموضوع! اسمي الموضوع-8

المشاكل التي يخلقها تعدد العمليات

في مثال الكتب، رأيت أن تعدد مؤشرات الترابط يحل مشكلات مهمة جدًا، كما يؤدي استخدامه إلى تسريع عمل برامجنا. في كثير من الحالات - عدة مرات. ولكن ليس من قبيل الصدفة أن يعتبر تعدد مؤشرات الترابط موضوعًا معقدًا. ففي النهاية، إذا تم استخدامه بشكل غير صحيح، فإنه يخلق مشاكل بدلاً من حلها. عندما أقول "خلق المشاكل"، لا أقصد شيئًا مجردًا. هناك مشكلتان محددتان يمكن أن تسببهما مؤشرات الترابط المتعددة: حالة الجمود وحالة السباق. حالة الجمود هي حالة تنتظر فيها سلاسل رسائل متعددة الموارد التي تشغلها بعضها البعض، ولا يمكن لأي منها الاستمرار في التنفيذ. سنتحدث عنها أكثر في المحاضرات القادمة، ولكن في الوقت الحالي سيكفي هذا المثال: تعدد مؤشرات الترابط في Java: الجوهر والمزايا والمزالق الشائعة - 4 تخيل أن الخيط 1 يعمل مع بعض الكائنات 1، والخيط 2 يعمل مع الكائن 2. البرنامج مكتوب هكذا :
  1. سيتوقف Thread-1 عن العمل مع Object-1 ويتحول إلى Object-2 بمجرد توقف Thread-2 عن العمل مع Object 2 ويتحول إلى Object-1.
  2. سيتوقف Thread-2 عن العمل مع Object-2 ويتحول إلى Object-1 بمجرد توقف Thread-1 عن العمل مع Object 1 ويتحول إلى Object-2.
حتى بدون معرفة عميقة بتعدد مؤشرات الترابط، يمكنك بسهولة أن تفهم أنه لن يتم تحقيق أي شيء. لن تتغير الخيوط أبدًا وستنتظر بعضها البعض إلى الأبد. يبدو الخطأ واضحا، لكنه في الواقع ليس كذلك. يمكنك السماح لها بسهولة بالدخول إلى البرنامج. سنلقي نظرة على أمثلة التعليمات البرمجية التي تسبب حالة توقف تام في المحاضرات التالية. بالمناسبة، لدى Quora مثال واقعي ممتاز يشرح معنى الجمود . "في بعض الولايات في الهند، لن يبيعوا لك أراضي زراعية إلا إذا كنت مسجلاً كمزارع. ومع ذلك، لن يتم تسجيلك كمزارع إذا كنت لا تمتلك أرضًا زراعية. عظيم، ماذا يمكنني أن أقول! :) الآن عن حالة السباق - حالة السباق. تعدد مؤشرات الترابط في Java: الجوهر والإيجابيات والمزالق الشائعة - 5حالة السباق هي عيب في التصميم في نظام أو تطبيق متعدد الخيوط، حيث يعتمد تشغيل النظام أو التطبيق على الترتيب الذي يتم به تنفيذ أجزاء من التعليمات البرمجية. تذكر المثال مع المواضيع قيد التشغيل:
public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("Выполнен поток " + getName());
   }
}

public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {

           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
والآن تخيل أن البرنامج هو المسؤول عن تشغيل الروبوت الذي يقوم بتحضير الطعام! الخيط 0 يخرج البيض من الثلاجة. تيار 1 يتحول على الموقد. يقوم Stream-2 بإخراج مقلاة ووضعها على الموقد. تيار 3 يشعل النار على الموقد. تيار 4 يصب الزيت في المقلاة. يكسر التيار 5 البيض ويصبه في المقلاة. يرمي الدفق 6 القذائف في سلة المهملات. يقوم Stream-7 بإزالة البيض المخفوق الجاهز من الحرارة. يضع Potok-8 البيض المخفوق على طبق. Stream-9 يغسل الأطباق. انظر إلى نتائج برنامجنا: تم تنفيذ مؤشر الترابط -0 تم تنفيذ مؤشر الترابط -2 تم ​​تنفيذ مؤشر الترابط -1 تم تنفيذ مؤشر الترابط -4 تم تنفيذ مؤشر الترابط -9 تم تنفيذ مؤشر الترابط -5 تم تنفيذ مؤشر الترابط -8 تم تنفيذ مؤشر الترابط -7 تم تنفيذ مؤشر الترابط -7 تم تنفيذ الخيط -3 تم تنفيذ الخيط -6 هل النص ممتع؟ :) وكل ذلك لأن تشغيل برنامجنا يعتمد على الترتيب الذي يتم به تنفيذ المواضيع. عند أدنى انتهاك للتسلسل، يتحول مطبخنا إلى جحيم، ويدمر الروبوت المجنون كل شيء من حوله. هذه أيضًا مشكلة شائعة في البرمجة متعددة الخيوط، والتي ستسمع عنها أكثر من مرة. وفي نهاية المحاضرة، أود أن أرشح لك كتابًا عن تعدد الخيوط.
تعدد مؤشرات الترابط في Java: الجوهر والإيجابيات والمزالق الشائعة - 6
تمت كتابة "Java Concurrency in Practice" في عام 2006، لكنها لم تفقد أهميتها. وهو يغطي البرمجة متعددة الخيوط في Java، بدءًا من الأساسيات وانتهاءً بقائمة الأخطاء والأنماط المضادة الأكثر شيوعًا. إذا قررت أن تصبح خبيرًا في البرمجة متعددة الخيوط، فهذا الكتاب يجب عليك قراءته. نراكم في المحاضرات القادمة! :)
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION