أسئلة مقابلة جافا متعددة الخيوط
العملية هي بيئة تشغيل قائمة بذاتها، والتي يمكن اعتبارها برنامجًا أو تطبيقًا. الخيط هو مهمة يتم تنفيذها في العملية. بيئة تشغيل Java هي عملية واحدة تحتوي على فئات وبرامج مختلفة. يمكن أن تسمى الخيوط عمليات خفيفة الوزن. تتطلب سلاسل العمليات موارد أقل لإنشاء العملية والتواجد فيها، ويمكنها مشاركة الموارد داخل العملية.
في البرامج متعددة الخيوط، يتم تنفيذ خيوط متعددة بشكل متزامن لتحسين كفاءة البرنامج. لن تدخل وحدة المعالجة المركزية في حالة الخمول لأن الخيط يحتاج إلى انتظار الموارد. تشترك مؤشرات الترابط المتعددة في ذاكرة الكومة، لذا من الأفضل إنشاء سلاسل رسائل متعددة لتنفيذ بعض المهام بدلاً من إنشاء عمليات متعددة. على سبيل المثال، تعد Servlets أفضل من CGI لأن Servlets تدعم الخيوط المتعددة بينما لا تدعمها CGI.
عندما نقوم بإنشاء موضوع في برنامج جافا، فإنه يسمى موضوع المستخدم. الخيط الخفي هو خيط يتم تنفيذه في الخلفية ولا يمنع JVM من الإنهاء. في حالة عدم تشغيل أي مؤشرات ترابط للمستخدم، يقوم JVM بإغلاق البرنامج والخروج. لا تزال الخيوط الفرعية التي تم إنشاؤها بواسطة خيط خفي هي سلاسل خفيّة.
هناك طريقتان لإنشاء مؤشر ترابط: إحداهما هي تنفيذ الواجهة القابلة للتشغيل، ثم تمريرها إلى مُنشئ مؤشر الترابط لإنشاء كائن مؤشر الترابط؛ والأخرى هي وراثة فئة الموضوع مباشرةً. إذا كنت تريد معرفة المزيد، يمكنك قراءة هذه المقالة حول كيفية إنشاء سلاسل رسائل في Java.
عندما نقوم بإنشاء سلسلة رسائل جديدة في برنامج Java، تكون حالتها جديدة. عندما نستدعي طريقة start () الخاصة بمؤشر الترابط، تتغير الحالة إلى Runnable. يقوم برنامج جدولة سلاسل الرسائل بتخصيص وقت وحدة المعالجة المركزية لسلاسل الرسائل الموجودة في تجمع سلاسل الرسائل القابلة للتشغيل وتغيير حالتها إلى قيد التشغيل. تتضمن حالات الخيط الأخرى انتظارًا ومحظورًا وميتًا. اقرأ هذه المقالة لمعرفة المزيد حول دورة حياة الخيط.
بالطبع، ولكن إذا قمنا باستدعاء طريقة run() الخاصة بـ Thread، فسوف تتصرف كطريقة عادية من أجل تنفيذ التعليمات البرمجية الخاصة بنا في موضوع جديد، يجب علينا استخدام طريقة Thread.start().
يمكننا استخدام طريقة Sleep () لفئة Thread لإيقاف مؤشر الترابط مؤقتًا لفترة من الوقت. تجدر الإشارة إلى أن هذا لا ينهي الخيط بمجرد استيقاظ الخيط من وضع السكون، ستتغير حالة الخيط إلى قابل للتشغيل، وسيتم تنفيذه وفقًا لجدول الخيط.
بشكل عام، كل خيط له أولوية، سيكون للسلاسل ذات الأولوية العالية الأولوية عند التشغيل، ولكن هذا يعتمد على تنفيذ جدولة الخيط، الذي يعتمد على نظام التشغيل. يمكننا تحديد أولوية سلاسل الرسائل، لكن هذا لا يضمن أن سلاسل الرسائل ذات الأولوية العالية سيتم تنفيذها قبل سلاسل الرسائل ذات الأولوية المنخفضة. أولوية الموضوع هي متغير int (من 1 إلى 10)، ويمثل 1 الأولوية الأدنى، ويمثل 10 الأولوية العليا.
جدولة سلسلة الرسائل هي خدمة نظام تشغيل مسؤولة عن تخصيص وقت وحدة المعالجة المركزية لسلاسل العمليات في حالة التشغيل. بمجرد إنشاء سلسلة رسائل وبدء تشغيلها، يعتمد تنفيذها على تنفيذ برنامج جدولة سلسلة الرسائل. يشير تقسيم الوقت إلى عملية تخصيص وقت وحدة المعالجة المركزية المتاحة لسلاسل العمليات القابلة للتشغيل المتاحة. يمكن أن يعتمد تخصيص وقت وحدة المعالجة المركزية على أولوية مؤشر الترابط أو الوقت الذي ينتظره مؤشر الترابط. لا يتم التحكم في جدولة سلسلة المحادثات بواسطة جهاز Java الظاهري، لذلك من الأفضل أن يتحكم فيها التطبيق (أي لا تجعل برنامجك يعتمد على أولوية سلسلة الرسائل).
تبديل السياق هو عملية تخزين واستعادة حالة وحدة المعالجة المركزية، والتي تمكن تنفيذ مؤشر الترابط من استئناف التنفيذ من نقطة الانقطاع. يعد تبديل السياق ميزة أساسية لأنظمة التشغيل متعددة المهام والبيئات متعددة الخيوط.
يمكننا استخدام طريقة Joint () لفئة Thread للتأكد من أن جميع سلاسل الرسائل التي أنشأها البرنامج تنتهي قبل خروج الطريقة main (). إليك مقالة عن طريقة Joint() لفئة Thread.
عندما يمكن مشاركة الموارد بين سلاسل العمليات، يعد الاتصال بين سلاسل العمليات وسيلة مهمة لتنسيقها. يمكن استخدام أساليب الانتظار ()/notify ()/notifyAll () في فئة الكائن للتواصل بين سلاسل الرسائل حول حالة أقفال الموارد. انقر هنا لمعرفة المزيد حول انتظار سلسلة المحادثات والإخطار والإخطار للجميع.
يحتوي كل كائن في Java على قفل (شاشة، والتي يمكن أن تكون أيضًا شاشة)، ويتم استخدام طرق مثل wait() و notify() لانتظار قفل الكائن أو إعلام سلاسل الرسائل الأخرى بأن شاشة الكائن متاحة. لا توجد أقفال أو مزامنات متاحة لأي كائن في سلاسل Java. ولهذا السبب تعد هذه الأساليب جزءًا من فئة الكائن بحيث يكون لكل فئة في Java طرق أساسية للاتصال بين الخيوط
عندما يحتاج الخيط إلى استدعاء طريقة الانتظار () لكائن ما، يجب أن يمتلك الخيط قفل الكائن، ثم سيحرر قفل الكائن ويدخل حالة الانتظار حتى تستدعي سلاسل الرسائل الأخرى طريقة الإخطار () على الكائن. وبالمثل، عندما يحتاج مؤشر ترابط إلى استدعاء طريقة notify () الخاصة بالكائن، فإنه سيحرر قفل الكائن حتى تتمكن مؤشرات الترابط الأخرى المنتظرة من الحصول على قفل الكائن. نظرًا لأن كل هذه الطرق تتطلب أن يحتفظ الخيط بقفل الكائن، وهو ما لا يمكن تحقيقه إلا من خلال المزامنة، فلا يمكن استدعاؤهم إلا في الطرق المتزامنة أو الكتل المتزامنة.
سيتم تشغيل أساليب السكون () والعائد () لفئة الموضوع على مؤشر الترابط الذي يتم تنفيذه حاليًا. لذلك ليس من المنطقي استدعاء هذه الأساليب على سلاسل رسائل أخرى تنتظر. لهذا السبب هذه الأساليب ثابتة. يمكنهم العمل في سلاسل المحادثات التي يتم تنفيذها حاليًا ومنع المبرمجين من التفكير بشكل خاطئ في أنه يمكن استدعاء هذه الأساليب في سلاسل رسائل أخرى غير قيد التشغيل.
هناك العديد من الطرق لضمان سلامة مؤشر الترابط في Java - المزامنة، واستخدام الفئات المتزامنة الذرية، وتنفيذ الأقفال المتزامنة، واستخدام الكلمة الأساسية المتطايرة، واستخدام الفئات غير القابلة للتغيير والفئات الآمنة لمؤشر الترابط. يمكنك معرفة المزيد في البرنامج التعليمي لسلامة الخيط.
عندما نستخدم الكلمة الأساسية المتقلبة لتعديل متغير، سيقرأ الخيط المتغير مباشرة ولن يخزنه مؤقتًا. وهذا يضمن أن المتغيرات التي يقرأها الخيط متوافقة مع تلك الموجودة في الذاكرة.
تعد الكتلة المتزامنة خيارًا أفضل لأنها لا تقفل الكائن بأكمله (بالطبع يمكنك أيضًا جعلها تقفل الكائن بأكمله). تقوم الطرق المتزامنة بقفل الكائن بأكمله، حتى لو كان هناك عدة كتل متزامنة غير مرتبطة في الفصل، مما يؤدي عادةً إلى توقفها عن التنفيذ والحاجة إلى الانتظار للحصول على قفل الكائن.
يمكن تعيين مؤشر الترابط كخيط خفي باستخدام طريقة setDaemon(true) لفئة Thread. تجدر الإشارة إلى أنه يجب استدعاء هذه الطريقة قبل استدعاء طريقة start ()، وإلا فسيتم طرح IllegalThreadStateException.
يتم استخدام ThreadLocal لإنشاء متغيرات مؤشر ترابط محلية. نحن نعلم أن جميع مؤشرات ترابط الكائن ستشارك متغيراتها العامة، لذا فإن هذه المتغيرات ليست آمنة لمؤشر الترابط. ولكن عندما لا نريد استخدام المزامنة، يمكننا اختيار متغيرات ThreadLocal.
سيكون لكل موضوع متغيرات الموضوع الخاصة به، ويمكنهم استخدام أساليب get()/set() للحصول على قيمهم الافتراضية أو تغيير قيمهم داخل الموضوع. تريد مثيلات ThreadLocal عادةً أن تكون حالة مؤشر الترابط المرتبطة بها خصائص ثابتة خاصة. في مقالة مثال ThreadLocal، يمكنك رؤية برنامج صغير حول ThreadLocal.
ThreadGroup هي فئة تهدف إلى توفير معلومات حول مجموعات سلاسل الرسائل.
واجهة برمجة تطبيقات ThreadGroup ضعيفة نسبيًا ولا توفر وظائف أكثر من Thread. لها وظيفتان رئيسيتان: إحداهما هي الحصول على قائمة مؤشرات الترابط النشطة في مجموعة مؤشرات الترابط؛ والأخرى هي تعيين معالج الاستثناء الذي لم يتم اكتشافه (معالج الاستثناء الذي لم يتم اكتشافه) لمؤشر الترابط. ومع ذلك، في Java 1.5، أضافت فئة Thread أيضًا طريقة setUncaughtExceptionHandler(UncaughtExceptionHandler eh)، لذا فإن ThreadGroup قديم ولا يوصى بمواصلة استخدامه.
t1.setUncaughtExceptionHandler(new UncaughtExceptionHandler(){ @Overridepublic void uncaughtException(Thread t, Throwable e) {System.out.println("حدث الاستثناء:"+e.getMessage());} });
تفريغ مؤشر الترابط عبارة عن قائمة بسلاسل رسائل JVM النشطة، وهي مفيدة جدًا لتحليل اختناقات النظام وحالات الجمود. هناك العديد من الطرق للحصول على عمليات تفريغ الخيوط - باستخدام Profiler، وأمر Kill -3، وأداة jstack، وما إلى ذلك. أفضل أداة jstack لأنها سهلة الاستخدام وتأتي مع JDK. نظرًا لأنها أداة تعتمد على المحطة الطرفية، يمكننا كتابة بعض البرامج النصية لإنشاء عمليات تفريغ سلاسل الرسائل بشكل دوري لتحليلها. اقرأ هذا المستند لمعرفة المزيد حول إنشاء عمليات تفريغ المواضيع.
يشير حالة توقف تام إلى الموقف الذي يتم فيه حظر أكثر من خيطين إلى الأبد. يتطلب هذا الموقف خيطين إضافيين على الأقل وأكثر من موردين.
لتحليل حالة الجمود، نحتاج إلى إلقاء نظرة على تفريغ مؤشر الترابط لتطبيق Java. نحتاج إلى معرفة المواضيع الموجودة في حالة "الحظر" والموارد التي تنتظرها. كل مورد لديه معرف فريد، باستخدام هذا المعرف يمكننا معرفة المواضيع التي تمتلك بالفعل قفل الكائن الخاص به.
يعد تجنب الأقفال المتداخلة، واستخدام الأقفال عند الحاجة فقط، وتجنب فترات الانتظار غير المحددة من الطرق الشائعة لتجنب حالات التوقف التام. اقرأ هذه المقالة للتعرف على كيفية تحليل حالات التوقف التام.
java.util.Timer هي فئة أدوات يمكن استخدامها لجدولة سلسلة رسائل ليتم تنفيذها في وقت محدد في المستقبل. يمكن استخدام فئة Timer لجدولة المهام لمرة واحدة أو المهام الدورية.
java.util.TimerTask هي فئة مجردة تنفذ الواجهة القابلة للتشغيل. نحتاج إلى وراثة هذه الفئة لإنشاء المهام المجدولة الخاصة بنا واستخدام Timer لجدولة تنفيذها.
فيما يلي أمثلة حول Java Timer.
يدير تجمع مؤشرات الترابط مجموعة من مؤشرات الترابط العاملة ويتضمن أيضًا قائمة انتظار لوضع المهام في انتظار التنفيذ.
يوفر java.util.concurrent.Executors تطبيقًا لواجهة java.util.concurrent.Executor لإنشاء تجمعات مؤشرات الترابط. يوضح مثال تجمع مؤشرات الترابط كيفية إنشاء واستخدام تجمع مؤشرات الترابط، أو اقرأ مثال ScheddedThreadPoolExecutor لمعرفة كيفية إنشاء مهمة دورية.
أسئلة المقابلة المتزامنة لجافا
تشير العملية الذرية إلى وحدة مهام التشغيل التي لا تتأثر بالعمليات الأخرى. تعد العمليات الذرية وسيلة ضرورية لتجنب عدم تناسق البيانات في بيئة متعددة الخيوط.
int++ ليست عملية ذرية، لذا عندما يقرأ أحد الخيوط قيمته ويضيف 1، قد يقرأ مؤشر ترابط آخر القيمة السابقة، مما قد يتسبب في حدوث خطأ.
لحل هذه المشكلة، يجب علينا التأكد من أن عملية الزيادة ذرية. قبل JDK1.5، يمكننا استخدام تقنية المزامنة للقيام بذلك. اعتبارًا من JDK 1.5، توفر الحزمة java.util.concurrent.atomic فئات تحميل من النوع int وطويلة تضمن تلقائيًا أن عملياتها ذرية ولا تتطلب استخدام المزامنة. يمكنك قراءة هذه المقالة للتعرف على فئات جافا الذرية.
توفر واجهة القفل عمليات قفل قابلة للتطوير أكثر من الطرق المتزامنة والكتل المتزامنة. إنها تسمح بهياكل أكثر مرونة يمكن أن يكون لها خصائص مختلفة تمامًا، ويمكن أن تدعم فئات متعددة مرتبطة بالكائنات الشرطية.
مزاياها هي:
اقرأ المزيد عن أمثلة القفل
تم تقديم إطار عمل Executor في Java 5 باستخدام واجهة java.util.concurrent.Executor. إطار عمل Executor هو إطار عمل للمهام غير المتزامنة التي يتم استدعاؤها وجدولتها وتنفيذها والتحكم فيها وفقًا لمجموعة من استراتيجيات التنفيذ.
يمكن أن يؤدي إنشاء سلسلة رسائل غير محدودة إلى تجاوز سعة ذاكرة التطبيق. لذا فإن إنشاء تجمع سلاسل الرسائل يعد حلاً أفضل لأنه يمكن أن يكون عدد الخيوط محدودًا ويمكن إعادة تدوير هذه الخيوط وإعادة استخدامها. من السهل جدًا إنشاء تجمع مؤشرات الترابط باستخدام إطار عمل Executor. اقرأ هذه المقالة لمعرفة كيفية إنشاء تجمع مؤشرات ترابط باستخدام إطار عمل Executor.
خصائص java.util.concurrent.BlockingQueue هي: عندما تكون قائمة الانتظار فارغة، سيتم حظر عملية الحصول على العناصر أو حذفها من قائمة الانتظار، أو عندما تكون قائمة الانتظار ممتلئة، سيتم حظر عملية إضافة عناصر إلى قائمة الانتظار .
لا تقبل قائمة الانتظار المحظورة القيم الخالية. عند محاولة إضافة قيمة فارغة إلى قائمة الانتظار، سيتم طرح NullPointerException.
تعد تطبيقات قائمة الانتظار المحظورة آمنة للخيوط، وجميع أساليب الاستعلام ذرية وتستخدم الأقفال الداخلية أو أشكال أخرى من التحكم في التزامن.
تعد واجهة BlockingQueue جزءًا من إطار عمل مجموعات Java وتستخدم بشكل أساسي لتنفيذ مشكلة المنتج والمستهلك.
اقرأ هذه المقالة للتعرف على كيفية تنفيذ مشكلة المنتج والمستهلك باستخدام قوائم الانتظار المحظورة.
قدمت Java 5 واجهة java.util.concurrent.Callable في حزمة التزامن، والتي تشبه إلى حد كبير واجهة Runnable، ولكن يمكنها إرجاع كائن أو طرح استثناء.
تستخدم الواجهة القابلة للاستدعاء أدوية عامة لتحديد نوع الإرجاع الخاص بها. توفر فئة Executors بعض الطرق المفيدة لتنفيذ المهام داخل Callable في تجمع مؤشرات الترابط. بما أن المهمة القابلة للاستدعاء متوازية، علينا أن ننتظر النتيجة التي تعود بها. كائن java.util.concurrent.Future يحل هذه المشكلة بالنسبة لنا. بعد أن يرسل تجمع مؤشرات الترابط المهمة القابلة للاستدعاء، يتم إرجاع كائن مستقبلي. وباستخدامه، يمكننا معرفة حالة المهمة القابلة للاستدعاء والحصول على نتيجة التنفيذ التي يتم إرجاعها بواسطة القابلة للاستدعاء. يوفر Future طريقة get() حتى نتمكن من انتظار انتهاء Callable والحصول على نتائج التنفيذ.
اقرأ هذه المقالة لمعرفة المزيد من الأمثلة حول Callable، Future.
FutureTask هو تطبيق أساسي لـ Future، والذي يمكننا استخدامه مع المنفذين لمعالجة المهام غير المتزامنة. عادةً لا نحتاج إلى استخدام فئة FutureTask، ولكنها تصبح مفيدة جدًا عندما نخطط لتجاوز بعض أساليب الواجهة المستقبلية والاحتفاظ بالتنفيذ الأساسي الأصلي. يمكننا فقط أن نرث منه ونتجاوز الأساليب التي نحتاجها. اقرأ مثال Java FutureTask لمعرفة كيفية استخدامه.
فئات مجموعة Java سريعة الفشل، مما يعني أنه عندما يتم تعديل المجموعة ويستخدم مؤشر ترابط مكررًا لاجتياز المجموعة، فإن أسلوب المكرر التالي () سوف يطرح استثناء ConcurrentModificationException.
تدعم الحاويات المتزامنة الاجتياز المتزامن والتحديثات المتزامنة.
الفئات الرئيسية هي ConcurrentHashMap وCopyOnWriteArrayList وCopyOnWriteArraySet. اقرأ هذه المقالة لمعرفة كيفية تجنب ConcurrentModificationException.
يوفر المنفذون بعض الأساليب المساعدة للفئات Executor وExecutorService وScheduledExecutorService وThreadFactory وCallable.
يمكن استخدام المنفذين لإنشاء تجمعات مؤشرات الترابط بسهولة.
النص الأصلي: Journaldev.com الترجمة: ifeve المترجم: Zheng Xudong