CodeGym /مدونة جافا /Random-AR /أفضل 50 سؤالاً وإجابات في مقابلات العمل لـ Java Core. الج...
John Squirrels
مستوى
San Francisco

أفضل 50 سؤالاً وإجابات في مقابلات العمل لـ Java Core. الجزء 2

نشرت في المجموعة
أفضل 50 سؤالاً وإجابات في مقابلات العمل لـ Java Core. الجزء 1 أفضل 50 سؤالاً وإجابات في مقابلات العمل لـ Java Core.  الجزء 2 - 1

تعدد الخيوط

24. كيف يمكنني إنشاء موضوع جديد في جافا؟

بطريقة أو بأخرى، يتم إنشاء سلسلة رسائل باستخدام فئة Thread. ولكن هناك طرق مختلفة للقيام بذلك …
  1. وراثة java.lang.Thread .
  2. قم بتنفيذ واجهة java.lang.Runnable — يأخذ مُنشئ فئة Thread كائنًا قابلاً للتشغيل.
دعونا نتحدث عن كل واحد منهم.

ترث فئة الموضوع

في هذه الحالة، نجعل صفنا يرث java.lang.Thread . يحتوي على طريقة run() ، وهذا ما نحتاجه تمامًا. ستكون كل حياة ومنطق الخيط الجديد بهذه الطريقة. إنه يشبه الطريقة الرئيسية للموضوع الجديد. بعد ذلك، كل ما تبقى هو إنشاء كائن من فئتنا واستدعاء طريقة start () . سيؤدي هذا إلى إنشاء موضوع جديد والبدء في تنفيذ منطقه. لنلقي نظرة:
/**
* An example of how to create threads by inheriting the {@link Thread} class.
*/
class ThreadInheritance extends Thread {

   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
   }

   public static void main(String[] args) {
       ThreadInheritance threadInheritance1 = new ThreadInheritance();
       ThreadInheritance threadInheritance2 = new ThreadInheritance();
       ThreadInheritance threadInheritance3 = new ThreadInheritance();
       threadInheritance1.start();
       threadInheritance2.start();
       threadInheritance3.start();
   }
}
سيكون إخراج وحدة التحكم شيئًا مثل هذا:
الموضوع-1 الموضوع-0 الموضوع-2
وهذا يعني أنه حتى هنا نرى أن سلاسل الرسائل لا يتم تنفيذها بالترتيب، بل بالطريقة التي يراها JVM مناسبة لتشغيلها :)

تنفيذ واجهة Runnable

إذا كنت ضد الميراث و/أو وراثة فئة أخرى بالفعل، فيمكنك استخدام واجهة java.lang.Runnable . هنا، نجعل فصلنا ينفذ هذه الواجهة من خلال تنفيذ طريقة run() ، تمامًا كما في المثال أعلاه. كل ما تبقى هو إنشاء كائنات الموضوع . يبدو أن المزيد من أسطر التعليمات البرمجية أسوأ. لكننا نعلم مدى ضرر الميراث وأنه من الأفضل تجنبه بكل الوسائل؛) ألقِ نظرة:
/**
* An example of how to create threads from the {@link Runnable} interface.
* It's easier than easy — we implement this interface and then pass an instance of our object
* to the constructor.
*/
class ThreadInheritance implements Runnable {

   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
   }

   public static void main(String[] args) {
       ThreadInheritance runnable1 = new ThreadInheritance();
       ThreadInheritance runnable2 = new ThreadInheritance();
       ThreadInheritance runnable3 = new ThreadInheritance();

       Thread threadRunnable1 = new Thread(runnable1);
       Thread threadRunnable2 = new Thread(runnable2);
       Thread threadRunnable3 = new Thread(runnable3);

       threadRunnable1.start();
       threadRunnable2.start();
       threadRunnable3.start();
   }
}
وهذه هي النتيجة:
الموضوع-0 الموضوع-1 الموضوع-2

25. ما الفرق بين العملية والخيط؟

أفضل 50 سؤالاً وإجابات في مقابلات العمل لـ Java Core.  الجزء 2 - 2تختلف العملية والخيط بالطرق التالية:
  1. يُسمى البرنامج قيد التشغيل عملية، لكن الخيط هو جزء من العملية.
  2. العمليات مستقلة، لكن الخيوط هي أجزاء من العملية.
  3. تحتوي العمليات على مساحات عناوين مختلفة في الذاكرة، لكن سلاسل العمليات تشترك في مساحة عنوان مشتركة.
  4. يعد تبديل السياق بين سلاسل العمليات أسرع من التبديل بين العمليات.
  5. يعد الاتصال بين العمليات أبطأ وأكثر تكلفة من الاتصال بين الخيوط.
  6. لا تؤثر أي تغييرات في العملية الأصلية على العملية الفرعية، لكن التغييرات في السلسلة الأصلية يمكن أن تؤثر على السلسلة الفرعية.

26. ما هي فوائد تعدد العمليات؟

  1. يتيح تعدد مؤشرات الترابط للتطبيق/البرنامج أن يكون دائمًا مستجيبًا للإدخال، حتى لو كان يقوم بالفعل بتشغيل بعض المهام في الخلفية؛
  2. تعدد مؤشرات الترابط يجعل من الممكن إكمال المهام بشكل أسرع، لأن سلاسل الرسائل تعمل بشكل مستقل؛
  3. يوفر تعدد مؤشرات الترابط استخدامًا أفضل للذاكرة المؤقتة، نظرًا لأن سلاسل العمليات يمكنها الوصول إلى موارد الذاكرة المشتركة؛
  4. يؤدي تعدد مؤشرات الترابط إلى تقليل عدد الخوادم المطلوبة، لأن خادمًا واحدًا يمكنه تشغيل عدة سلاسل رسائل في وقت واحد.

27. ما هي الحالات في دورة حياة الخيط؟

أفضل 50 سؤالاً وإجابات في مقابلات العمل لـ Java Core.  الجزء 2 - 3
  1. جديد: في هذه الحالة، يتم إنشاء كائن مؤشر الترابط باستخدام عامل التشغيل الجديد، ولكن لا يوجد مؤشر ترابط جديد بعد. لا يبدأ الخيط حتى نستدعي طريقة start () .
  2. قابل للتشغيل: في هذه الحالة، يكون الخيط جاهزًا للتشغيل بعد البدء () تسمى الطريقة . ومع ذلك، لم يتم تحديده بعد بواسطة برنامج جدولة سلسلة المحادثات.
  3. قيد التشغيل: في هذه الحالة، يقوم برنامج جدولة الخيط باختيار خيط من حالة الاستعداد، ويتم تشغيله.
  4. انتظار/محظور: في هذه الحالة، لا يتم تشغيل سلسلة رسائل، ولكنها لا تزال حية أو تنتظر اكتمال سلسلة محادثات أخرى.
  5. ميت/منتهٍ: عندما يخرج مؤشر ترابط من طريقة التشغيل () ، فإنه يكون في حالة ميتة أو منتهية.

28. هل من الممكن تشغيل الموضوع مرتين؟

لا، لا يمكننا إعادة تشغيل الخيط، لأنه بعد بدء تشغيل الخيط، فإنه ينتقل إلى الحالة الميتة. إذا حاولنا بدء سلسلة محادثات مرتين، فسيتم طرح استثناء java.lang.IllegalThreadStateException . لنلقي نظرة:
class DoubleStartThreadExample extends Thread {

   /**
    * Simulate the work of a thread
    */
   public void run() {
	// Something happens. At this state, this is not essential.
   }

   /**
    * Start the thread twice
    */
   public static void main(String[] args) {
       DoubleStartThreadExample doubleStartThreadExample = new DoubleStartThreadExample();
       doubleStartThreadExample.start();
       doubleStartThreadExample.start();
   }
}
سيكون هناك استثناء بمجرد وصول التنفيذ إلى البداية الثانية لنفس الموضوع. جربه بنفسك؛) من الأفضل أن ترى هذا مرة واحدة بدلاً من أن تسمع عنه مائة مرة.

29. ماذا لو قمت باستدعاء run() مباشرة دون استدعاء start()؟

نعم، يمكنك بالتأكيد استدعاء طريقة التشغيل () ولكن لن يتم إنشاء مؤشر ترابط جديد، ولن يتم تشغيل الطريقة على مؤشر ترابط منفصل. في هذه الحالة، لدينا كائن عادي يستدعي طريقة عادية. إذا كنا نتحدث عن طريقة start() ، فهذا أمر آخر. عندما يتم استدعاء هذه الطريقة، يبدأ JVM موضوعًا جديدًا. هذا الموضوع، بدوره، يستدعي طريقتنا؛) ألا تصدق ذلك؟ هنا، جربها:
class ThreadCallRunExample extends Thread {

   public void run() {
       for (int i = 0; i < 5; i++) {
           System.out.print(i);
       }
   }

   public static void main(String args[]) {
       ThreadCallRunExample runExample1 = new ThreadCallRunExample();
       ThreadCallRunExample runExample2 = new ThreadCallRunExample();

       // Two ordinary methods will be called in the main thread, one after the other.
       runExample1.run();
       runExample2.run();
   }
}
وسيبدو إخراج وحدة التحكم كما يلي:
0123401234
كما ترى، لم يتم إنشاء أي موضوع. كل شيء سار كما هو الحال في الفصل العادي. أولا، تم تنفيذ طريقة الكائن الأول، ثم الثاني.

30. ما هو الخيط الخفي؟

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

لماذا تقوم Java بإنهاء مؤشر ترابط خفي؟

الغرض الوحيد من الخيط الخفي هو توفير دعم الخلفية لخيط المستخدم. وفقًا لذلك، إذا تم إنهاء الخيط الرئيسي، فسيقوم JVM تلقائيًا بإنهاء جميع سلاسل العمليات الخاصة به.

طرق فئة الموضوع

توفر فئة java.lang.Thread طريقتين للعمل مع مؤشر ترابط خفي:
  1. public void setDaemon(boolean Status) - تشير هذه الطريقة إلى ما إذا كان هذا سيكون خيطًا خفيًا. الافتراضي هو خطأ . هذا يعني أنه لن يتم إنشاء أي سلاسل رسائل خفية إلا إذا ذكرت ذلك على وجه التحديد.
  2. public boolean isDaemon() — هذه الطريقة هي في الأساس أداة للحصول على المتغير الخفي ، الذي قمنا بتعيينه باستخدام الطريقة السابقة.
مثال:
class DaemonThreadExample extends Thread {

   public void run() {
       // Checks whether this thread is a daemon
       if (Thread.currentThread().isDaemon()) {
           System.out.println("daemon thread");
       } else {
           System.out.println("user thread");
       }
   }

   public static void main(String[] args) {
       DaemonThreadExample thread1 = new DaemonThreadExample();
       DaemonThreadExample thread2 = new DaemonThreadExample();
       DaemonThreadExample thread3 = new DaemonThreadExample();

       // Make thread1 a daemon thread.
       thread1.setDaemon(true);

       System.out.println("daemon? " + thread1.isDaemon());
       System.out.println("daemon? " + thread2.isDaemon());
       System.out.println("daemon? " + thread3.isDaemon());

       thread1.start();
       thread2.start();
       thread3.start();
   }
}
إخراج وحدة التحكم:
الشيطان؟ الشيطان الحقيقي؟ شيطان كاذب؟ موضوع المستخدم موضوع المستخدم موضوع الشيطان كاذبة
من المخرجات، نرى أنه داخل الخيط نفسه، يمكننا استخدام الطريقة الثابتة currentThread() لمعرفة أي خيط هو. وبدلاً من ذلك، إذا كان لدينا مرجع لكائن الخيط، فيمكننا أيضًا اكتشافه مباشرة منه. وهذا يوفر المستوى اللازم من قابلية التكوين.

31. هل من الممكن جعل الخيط خفيًا بعد إنشائه؟

لا، إذا حاولت القيام بذلك، فسوف تحصل على IllegalThreadStateException . هذا يعني أنه لا يمكننا إنشاء خيط خفي إلا قبل أن يبدأ. مثال:
class SetDaemonAfterStartExample extends Thread {

   public void run() {
       System.out.println("Working...");
   }

   public static void main(String[] args) {
       SetDaemonAfterStartExample afterStartExample = new SetDaemonAfterStartExample();
       afterStartExample.start();

       // An exception will be thrown here
       afterStartExample.setDaemon(true);
   }
}
إخراج وحدة التحكم:
العمل... استثناء في مؤشر الترابط "الرئيسي" java.lang.IllegalThreadStateException في java.lang.Thread.setDaemon(Thread.java:1359) في SetDaemonAfterStartExample.main(SetDaemonAfterStartExample.java:14)

32. ما هو خطاف الاغلاق؟

خطاف إيقاف التشغيل هو سلسلة رسائل يتم استدعاؤها ضمنيًا قبل إيقاف تشغيل جهاز Java الظاهري (JVM). وبالتالي، يمكننا استخدامه لتحرير مورد أو حفظ الحالة عندما يتم إيقاف تشغيل جهاز Java الظاهري بشكل طبيعي أو غير طبيعي. يمكننا إضافة خطاف إيقاف التشغيل باستخدام الطريقة التالية:
Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());
كما هو موضح في المثال:
/**
* A program that shows how to start a shutdown hook thread,
* which will be executed right before the JVM shuts down
*/
class ShutdownHookThreadExample extends Thread {

   public void run() {
       System.out.println("shutdown hook executed");
   }

   public static void main(String[] args) {

       Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());

       System.out.println("Now the program is going to fall asleep. Press Ctrl+C to terminate it.");
       try {
           Thread.sleep(60000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
}
إخراج وحدة التحكم:
الآن سوف ينام البرنامج. اضغط على Ctrl+C لإنهائه. تم تنفيذ خطاف الإغلاق

33. ما هو التزامن؟

في Java، المزامنة هي القدرة على التحكم في وصول سلاسل رسائل متعددة إلى أي مورد مشترك. عندما تحاول عدة سلاسل تنفيذ نفس المهمة في وقت واحد، قد تحصل على نتيجة غير صحيحة. لإصلاح هذه المشكلة، تستخدم Java المزامنة، والتي تسمح بتشغيل مؤشر ترابط واحد فقط في المرة الواحدة. يمكن تحقيق المزامنة بثلاث طرق:
  • مزامنة الطريقة
  • مزامنة كتلة محددة
  • مزامنة ثابتة

مزامنة الطريقة

يتم استخدام أسلوب متزامن لتأمين كائن لأي مورد مشترك. عندما يستدعي خيط طريقة متزامنة، فإنه يحصل تلقائيًا على قفل الكائن ويحرره عندما يكمل الخيط مهمته. لإنجاز هذا العمل، تحتاج إلى إضافة الكلمة الأساسية المتزامنة . يمكننا أن نرى كيف يعمل هذا من خلال النظر في مثال:
/**
* An example where we synchronize a method. That is, we add the synchronized keyword to it.
* There are two authors who want to use one printer. Each of them has composed their own poems
* And of course they don’t want their poems mixed up. Instead, they want work to be performed in * * * order for each of them
*/
class Printer {

   synchronized void print(List<String> wordsToPrint) {
       wordsToPrint.forEach(System.out::print);
       System.out.println();
   }

   public static void main(String args[]) {
       // One object for two threads
       Printer printer  = new Printer();

       // Create two threads
       Writer1 writer1 = new Writer1(printer);
       Writer2 writer2 = new Writer2(printer);

       // Start them
       writer1.start();
       writer2.start();
   }
}

/**
* Author No. 1, who writes an original poem.
*/
class Writer1 extends Thread {
   Printer printer;

   Writer1(Printer printer) {
       this.printer = printer;
   }

   public void run() {
       List<string> poem = Arrays.asList("I ", this.getName(), " Write", " A Letter");
       printer.print(poem);
   }

}

/**
* Author No. 2, who writes an original poem.
*/
class Writer2 extends Thread {
   Printer printer;

   Writer2(Printer printer) {
       this.printer = printer;
   }

   public void run() {
       List<String> poem = Arrays.asList("I Do Not ", this.getName(), " Not Write", " No Letter");
       printer.print(poem);
   }
}
وإخراج وحدة التحكم هو هذا:
أنا الموضوع-0 أكتب رسالة لا أكتب-1 لا أكتب أي حرف

كتلة المزامنة

يمكن استخدام الكتلة المتزامنة لإجراء المزامنة على أي مورد معين في إحدى الطرق. لنفترض أنه بطريقة كبيرة (نعم، لا يجب أن تكتبها، ولكن في بعض الأحيان تحدث) تحتاج إلى مزامنة قسم صغير فقط لسبب ما. إذا قمت بوضع كافة التعليمات البرمجية الخاصة بهذه الطريقة في كتلة متزامنة، فستعمل بنفس الطريقة التي تعمل بها الطريقة المتزامنة. بناء الجملة يبدو مثل هذا:
synchronized ("object to be locked") {
   // The code that must be protected
}
لتجنب تكرار المثال السابق، سنقوم بإنشاء سلاسل رسائل باستخدام فئات مجهولة، أي أننا سننفذ على الفور الواجهة القابلة للتشغيل.
/**
* This is how a synchronization block is added.
* Inside the block, you need to specify which object's mutex will be acquired.
*/
class Printer {

   void print(List<String> wordsToPrint) {
       synchronized (this) {
           wordsToPrint.forEach(System.out::print);
       }
       System.out.println();
   }

   public static void main(String args[]) {
       // One object for two threads
       Printer printer = new Printer();

       // Create two threads
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("I ", "Writer1", " Write", " A Letter");
               printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("I Do Not ", "Writer2", " Not Write", " No Letter");
               printer.print(poem);
           }
       });

       // Start them
       writer1.start();
       writer2.start();
   }
}

}
وإخراج وحدة التحكم هو هذا:
أنا كاتب 1 أكتب رسالة لا أكتب 2 لا أكتب أي رسالة

مزامنة ثابتة

إذا قمت بمزامنة طريقة ثابتة، فسيتم القفل على الفئة، وليس على الكائن. في هذا المثال، نقوم بإجراء مزامنة ثابتة من خلال تطبيق الكلمة الأساسية المتزامنة على طريقة ثابتة:
/**
* This is how a synchronization block is added.
* Inside the block, you need to specify which object's mutex will be acquired.
*/
class Printer {

   static synchronized void print(List<String> wordsToPrint) {
       wordsToPrint.forEach(System.out::print);
       System.out.println();
   }

   public static void main(String args[]) {

       // Create two threads
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("I ", "Writer1", " Write", " A Letter");
               Printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("I Do Not ", "Writer2", " Not Write", " No Letter");
               Printer.print(poem);
           }
       });

       // Start them
       writer1.start();
       writer2.start();
   }
}
وإخراج وحدة التحكم هو هذا:
أنا لا أكتب 2 لا أكتب أي رسالة أنا كاتب 1 أكتب رسالة

34. ما هو المتغير المتقلب؟

في البرمجة ذات مؤشرات الترابط المتعددة، يتم استخدام الكلمة الأساسية المتقلبة لسلامة الخيط. عندما يتم تعديل متغير قابل للتغيير، يكون التغيير مرئيًا لجميع المواضيع الأخرى، لذلك يمكن استخدام المتغير بواسطة مؤشر ترابط واحد في المرة الواحدة. باستخدام الكلمة الأساسية المتقلبة ، يمكنك ضمان أن يكون المتغير آمنًا لمؤشر الترابط ومخزنًا في الذاكرة المشتركة، وأن سلاسل الرسائل لن تخزنه في ذاكرة التخزين المؤقت الخاصة بها. ماذا يعني هذا تبدو؟
private volatile AtomicInteger count;
نحن فقط نضيف المتقلبة إلى المتغير. لكن ضع في اعتبارك أن هذا لا يعني السلامة الكاملة للخيط. بعد كل شيء، قد لا تكون العمليات على المتغير ذرية. ومع ذلك، يمكنك استخدام الفئات الذرية التي تقوم بالعمليات ذريًا، أي في تعليمات واحدة لوحدة المعالجة المركزية. هناك العديد من هذه الفئات في الحزمة java.util.concurrent.atomic .

35. ما هو الجمود؟

في Java، الجمود هو شيء يمكن أن يحدث كجزء من تعدد العمليات. يمكن أن يحدث حالة توقف تام عندما ينتظر الخيط قفل الكائن الذي حصل عليه خيط آخر، وينتظر الخيط الثاني قفل الكائن الذي حصل عليه الخيط الأول. هذا يعني أن الخيطين ينتظران بعضهما البعض، ولا يمكن متابعة تنفيذ التعليمات البرمجية الخاصة بهما. أفضل 50 سؤالاً وإجابات في مقابلات العمل لـ Java Core.  الجزء 2 - 4لنفكر في مثال يحتوي على فئة تقوم بتنفيذ Runnable. يأخذ منشئه موردين. تحصل طريقة run() على القفل بالترتيب. إذا قمت بإنشاء كائنين من هذه الفئة، وقمت بتمرير الموارد بترتيب مختلف، فمن الممكن أن تصل بسهولة إلى طريق مسدود:
class DeadLock {

   public static void main(String[] args) {
       final Integer r1 = 10;
       final Integer r2 = 15;

       DeadlockThread threadR1R2 = new DeadlockThread(r1, r2);
       DeadlockThread threadR2R1 = new DeadlockThread(r2, r1);

       new Thread(threadR1R2).start();
       new Thread(threadR2R1).start();
   }
}

/**
* A class that accepts two resources.
*/
class DeadlockThread implements Runnable {

   private final Integer r1;
   private final Integer r2;

   public DeadlockThread(Integer r1, Integer r2) {
       this.r1 = r1;
       this.r2 = r2;
   }

   @Override
   public void run() {
       synchronized (r1) {
           System.out.println(Thread.currentThread().getName() + " acquired resource: " + r1);

           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }

           synchronized (r2) {
               System.out.println(Thread.currentThread().getName() + " acquired resource: " + r2);
           }
       }
   }
}
إخراج وحدة التحكم:
حصل الخيط الأول على المورد الأول حصل الخيط الثاني على المورد الثاني

36. كيف تتجنب الجمود؟

ولأننا نعرف كيف يحدث الجمود، يمكننا استخلاص بعض الاستنتاجات...
  • في المثال أعلاه، يحدث الجمود بسبب حقيقة أننا قمنا بتداخل القفل. أي أن لدينا كتلة متزامنة داخل كتلة متزامنة. لتجنب ذلك، بدلاً من التداخل، تحتاج إلى إنشاء طبقة تجريد أعلى جديدة، ونقل المزامنة إلى المستوى الأعلى، وإزالة القفل المتداخل.
  • كلما زاد عدد عمليات القفل، زادت احتمالية حدوث طريق مسدود. لذلك، في كل مرة تقوم فيها بإضافة كتلة متزامنة، عليك أن تفكر فيما إذا كنت في حاجة إليها حقًا وما إذا كان يمكنك تجنب إضافة كتلة جديدة.
  • باستخدام Thread.join() . يمكنك أيضًا الدخول في حالة توقف تام أثناء انتظار مؤشر ترابط لآخر. لتجنب هذه المشكلة، قد تفكر في تعيين مهلة للأسلوب join() .
  • إذا كان لدينا موضوع واحد، فلن يكون هناك طريق مسدود ;)

37. ما هي حالة السباق؟

إذا كانت السباقات الواقعية تتضمن سيارات، فإن السباقات في تعدد الخيوط تتضمن خيوطًا. لكن لماذا؟ :/ هناك خيطان قيد التشغيل ويمكنهما الوصول إلى نفس الكائن. وقد يحاولون تحديث حالة الكائن المشترك في نفس الوقت. كل شيء واضح حتى الآن، أليس كذلك؟ يتم تنفيذ الخيوط إما حرفيًا بالتوازي (إذا كان المعالج يحتوي على أكثر من نواة واحدة) أو بشكل تسلسلي، حيث يقوم المعالج بتخصيص شرائح زمنية مشذرة. لا يمكننا إدارة هذه العمليات. هذا يعني أنه عندما يقرأ أحد الخيوط البيانات من كائن ما، لا يمكننا ضمان أنه سيكون لديه الوقت لتغيير الكائن قبل أن يقوم خيط آخر بذلك. تنشأ مثل هذه المشكلات عندما يكون لدينا مجموعات "التحقق والتصرف" هذه. ماذا يعني ذالك؟ لنفترض أن لدينا عبارة if التي يغير جسمها حالة if نفسها، على سبيل المثال:
int z = 0;

// Check
if (z < 5) {
// Act
   z = z + 5;
}
يمكن لخيطين إدخال كتلة التعليمات البرمجية هذه في وقت واحد عندما لا يزال z صفرًا ومن ثم يمكن لكلا الخيطين تغيير قيمته. ونتيجة لذلك، لن نحصل على القيمة المتوقعة وهي 5. وبدلاً من ذلك، سنحصل على 10. كيف يمكنك تجنب ذلك؟ تحتاج إلى الحصول على قفل قبل التحقق والتصرف، ثم تحرير القفل بعد ذلك. أي أنك تحتاج إلى إدخال الخيط الأول في كتلة if ، وتنفيذ جميع الإجراءات، وتغيير z ، وعندها فقط تمنح الخيط التالي الفرصة للقيام بنفس الشيء. لكن الخيط التالي لن يدخل إلى كتلة if ، لأن z أصبح الآن 5:
// Acquire the lock for z
if (z < 5) {
   z = z + 5;
}
// Release z's lock
===================================================

بدلا من الاستنتاج

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

وكما هو الحال دائمًا، إليك بعض الروابط المفيدة:

شكرا لكم جميعا على القراءة. نراكم قريبًا :) ملفي الشخصي على GitHub أفضل 50 سؤالاً وإجابات في مقابلات العمل لـ Java Core.  الجزء 2 - 5
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION