CodeGym /จาวาบล็อก /สุ่ม /คำถามและคำตอบสัมภาษณ์งาน 50 อันดับแรกสำหรับ Java Core ส่ว...
John Squirrels
ระดับ
San Francisco

คำถามและคำตอบสัมภาษณ์งาน 50 อันดับแรกสำหรับ Java Core ส่วนที่ 2

เผยแพร่ในกลุ่ม
คำถามและคำตอบสัมภาษณ์งาน 50 อันดับแรกสำหรับ Java Core ส่วนที่ 1คำถามและคำตอบสัมภาษณ์งาน 50 อันดับแรกสำหรับ Java Core  ตอนที่ 2 - 1

มัลติเธรด

24. ฉันจะสร้างเธรดใหม่ใน Java ได้อย่างไร

ไม่ทางใดก็ทางหนึ่งเธรดถูกสร้างขึ้นโดยใช้คลาสเธรด แต่มีหลายวิธีในการทำเช่นนี้ ...
  1. สืบทอดjava.lang.Thread _
  2. ใช้ อินเทอร์เฟซ java.lang.Runnable — ตัวสร้างของ คลาสเธรดรับวัตถุที่รันได้
มาพูดถึงแต่ละคนกันดีกว่า

สืบทอดคลาสเธรด

ในกรณีนี้ เราทำให้คลาสของเราสืบทอด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 เห็นว่าเหมาะสมที่จะรัน :)

ใช้อินเทอร์เฟซที่เรียกใช้ได้

หากคุณต่อต้านการสืบทอดและ/หรือสืบทอดคลาสอื่นอยู่แล้ว คุณสามารถใช้อินเทอร์เฟซ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. Runnable:ในสถานะนี้ เธรดพร้อมที่จะทำงานหลังจาก start() เรียกว่าเมธอด อย่างไรก็ตาม ยังไม่ได้เลือกโดยตัวกำหนดตารางเวลาของเธรด
  3. กำลังทำงาน:ในสถานะนี้ ตัวกำหนดตารางเวลาเธรดจะเลือกเธรดจากสถานะพร้อม และรัน
  4. กำลังรอ/ถูกบล็อก:ในสถานะนี้ เธรดไม่ทำงาน แต่ยังคงอยู่หรือรอให้เธรดอื่นทำงานให้เสร็จ
  5. ตาย/สิ้นสุด:เมื่อเธรดออกจาก เมธอด run()จะอยู่ในสถานะตายหรือสิ้นสุด

28. เป็นไปได้ไหมที่จะรันเธรดสองครั้ง?

ไม่ได้ เราไม่สามารถรีสตาร์ทเธรดได้ เนื่องจากหลังจากเธรดเริ่มต้นและรัน เธรดจะเข้าสู่สถานะ Dead หากเราพยายามเริ่มเธรดสองครั้ง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() ล่ะ?

ได้ คุณสามารถเรียกใช้ เมธอด run() ได้อย่างแน่นอน แต่จะไม่มีการสร้างเธรดใหม่ และเมธอดจะไม่ทำงานบนเธรดแยกต่างหาก ในกรณีนี้ เรามีวัตถุธรรมดาที่เรียกใช้เมธอดธรรมดา หากเรากำลังพูดถึง เมธอด 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. ดีมอนเธรดคืออะไร?

เธรดdaemonเป็นเธรดที่ดำเนินงานที่มีลำดับความสำคัญต่ำกว่าเธรดอื่น กล่าวอีกนัยหนึ่ง หน้าที่ของมันคือการทำงานเสริมที่ต้องทำร่วมกับเธรด (หลัก) อื่นเท่านั้น มีเธรด daemon มากมายที่ทำงานโดยอัตโนมัติ เช่น การรวบรวมขยะ โปรแกรมจัดลำดับขั้นสุดท้าย เป็นต้น

เหตุใด Java จึงยุติเธรด daemon

จุดประสงค์เดียวของเธรด daemon คือให้การสนับสนุนเบื้องหลังแก่เธรดของผู้ใช้ ดังนั้น หากเธรดหลักถูกยกเลิก JVM จะยุติเธรด daemon ทั้งหมดโดยอัตโนมัติ

เมธอดของคลาสเธรด

คลาสjava.lang.Threadจัดเตรียมสองวิธีสำหรับการทำงานกับเธรด daemon:
  1. โมฆะสาธารณะ setDaemon (สถานะบูลีน) — วิธีการนี้ระบุว่านี่จะเป็นเธรดภูตหรือไม่ ค่าดีฟอลต์เป็นเท็จ ซึ่งหมายความว่าจะไม่มีการสร้างเธรด daemon เว้นแต่คุณจะระบุไว้อย่างชัดเจน
  2. บูลีนสาธารณะ isDaemon() — เมธอดนี้เป็น getter สำหรับ ตัวแปร daemonซึ่งเราตั้งค่าโดยใช้เมธอดก่อนหน้า
ตัวอย่าง:

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();
   }
}
เอาต์พุตคอนโซล:
ภูต? ภูตที่แท้จริง? ภูตปลอม? เธรดภูตเท็จ เธรดผู้ใช้ เธรดผู้ใช้
จากผลลัพธ์ เราจะเห็นว่าภายในเธรดเอง เราสามารถใช้เมธอด static currentThread()เพื่อค้นหาว่าเธรดนั้นเป็นเธรดใด อีกทางหนึ่ง หากเรามีการอ้างอิงถึงเธรดออบเจกต์ เราก็สามารถค้นหาได้โดยตรงจากออบเจกต์นั้น สิ่งนี้ให้ความสามารถในการกำหนดค่าในระดับที่จำเป็น

31. เป็นไปได้ไหมที่จะสร้างเธรดเป็น daemon หลังจากสร้างแล้ว

ไม่ หากคุณพยายามทำเช่นนี้ คุณจะได้รับIllegalThreadStateException ซึ่งหมายความว่าเราสามารถสร้างเธรด daemon ได้ก่อนที่จะเริ่มเท่านั้น ตัวอย่าง:

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);
   }
}
เอาต์พุตคอนโซล:
กำลังทำงาน... ข้อยกเว้นในเธรด "main" java.lang.IllegalThreadStateException ที่ java.lang.Thread.setDaemon(Thread.java:1359) ที่ SetDaemonAfterStartExample.main(SetDaemonAfterStartExample.java:14)

32. ตะขอปิดเครื่องคืออะไร?

hook การปิดระบบคือเธรดที่ถูกเรียกโดยปริยายก่อนที่ Java virtual machine (JVM) จะถูกปิด ดังนั้นเราจึงสามารถใช้มันเพื่อปล่อยทรัพยากรหรือบันทึกสถานะเมื่อ Java virtual machine ปิดตัวลงตามปกติหรือผิดปกติ เราสามารถเพิ่มhook การปิดระบบได้โดยใช้วิธีการต่อไปนี้:

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;
เราเพียงแค่เพิ่มความผันผวนให้กับตัวแปร แต่โปรดจำไว้ว่านี่ไม่ได้หมายความว่าความปลอดภัยของเธรดที่สมบูรณ์... ท้ายที่สุดแล้ว การดำเนินการกับตัวแปรอาจไม่ใช่ระดับปรมาณู ที่กล่าวว่า คุณสามารถใช้ คลาส Atomicที่ทำงานในระดับ atomic ได้ เช่น ในคำสั่ง CPU เดียว มีคลาสดังกล่าวมากมายในแพ็คเกจ 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