JavaRush /จาวาบล็อก /Random-TH /คำถามและคำตอบสัมภาษณ์ Java Core 50 อันดับแรก ส่วนที่ 3
Roman Beekeeper
ระดับ

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

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

มัลติเธรด

37. จะสร้างเธรดใหม่ (โฟลว์) ใน Java ได้อย่างไร?

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

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

เพื่อให้งานนี้สำเร็จ ในชั้นเรียนของเรา เราสืบทอดมาจากjava.lang.Thread. มันมียาบ้าrun()ซึ่งเป็นสิ่งที่เราต้องการจริงๆ ชีวิตและตรรกะทั้งหมดของเธรดใหม่จะอยู่ในวิธีนี้ นี่เป็นวิธีชนิดหนึ่งmainสำหรับเธรดใหม่ หลังจากนี้ สิ่งที่เหลืออยู่คือการสร้างอ็อบเจ็กต์ของคลาสของเราและดำเนินการเมธอดstart()ซึ่งจะสร้างเธรดใหม่และรันตรรกะที่เขียนไว้ มาดูกัน:
/**
* Пример того, How создавать треды путем наследования {@link Thread} класса.
*/
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();
   }
}
ผลลัพธ์ไปยังคอนโซลจะเป็นดังนี้:

Thread-1
Thread-0
Thread-2
นั่นคือแม้ที่นี่เราจะเห็นว่าเธรดไม่ได้ถูกดำเนินการตามลำดับ แต่ตามที่ JVM ตัดสินใจ)

การใช้อินเทอร์เฟซ Runnable

หากคุณต่อต้านการสืบทอดและ/หรือสืบทอดคลาสอื่นคลาสใดคลาสหนึ่งอยู่แล้ว คุณสามารถใช้java.lang.Runnable. ในชั้นเรียนของเรา เราใช้อินเทอร์เฟซนี้และใช้เมธอดrun()ดังที่ปรากฏในตัวอย่างนั้น คุณเพียงแค่ต้องสร้างวัตถุThreadเพิ่มเติม ดูเหมือนว่าบรรทัดจะแย่ลง แต่เรารู้ว่ามรดกนั้นอันตรายแค่ไหนและเป็นการดีกว่าที่จะหลีกเลี่ยงมันทุกวิถีทาง ;) มาดูกันดีกว่า:
/**
* Пример того, How создавать треды из интерфейса {@link Runnable}.
* Здесь проще простого - реализуем этот интерфейс и потом передаем в конструктор
* экземпляр реализуемого an object.
*/
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();
   }
}
และผลการดำเนินการ:

Thread-0
Thread-1
Thread-2

38. อะไรคือความแตกต่างระหว่างกระบวนการและเธรด?

คำถามและคำตอบสัมภาษณ์ Java Core 50 อันดับแรก  ส่วนที่ 3 - 1มีความแตกต่างระหว่างกระบวนการและเธรดดังต่อไปนี้:
  1. โปรแกรมที่กำลังดำเนินการเรียกว่ากระบวนการ ในขณะที่ Thread เป็นส่วนย่อยของกระบวนการ
  2. กระบวนการเป็นอิสระ ในขณะที่เธรดเป็นส่วนย่อยของกระบวนการ
  3. กระบวนการมีพื้นที่ที่อยู่ที่แตกต่างกันในหน่วยความจำ ในขณะที่เธรดมีพื้นที่ที่อยู่ทั่วไป
  4. การสลับบริบทระหว่างเธรดทำได้เร็วกว่าเมื่อเปรียบเทียบกับกระบวนการ
  5. การสื่อสารระหว่างกระบวนการช้ากว่าและมีราคาแพงกว่าการสื่อสารแบบอินเตอร์เธรด
  6. การเปลี่ยนแปลงใดๆ ในกระบวนการหลักจะไม่ส่งผลต่อกระบวนการลูก ในขณะที่การเปลี่ยนแปลงในเธรดหลักอาจส่งผลต่อเธรดย่อย

39. ข้อดีของมัลติเธรดคืออะไร?

คำถามและคำตอบสัมภาษณ์ Java Core 50 อันดับแรก  ส่วนที่ 3 - 2
  1. มัลติเธรดช่วยให้แอปพลิเคชัน/โปรแกรมตอบสนองต่ออินพุตได้ตลอดเวลา แม้ว่าจะกำลังทำงานในเบื้องหลังอยู่บ้างก็ตาม
  2. มัลติเธรดช่วยให้คุณทำงานเสร็จเร็วขึ้นเนื่องจากเธรดดำเนินการอย่างอิสระ
  3. มัลติเธรดช่วยให้ใช้งานแคชได้ดีขึ้น เนื่องจากเธรดใช้ทรัพยากรหน่วยความจำร่วมกัน
  4. มัลติเธรดจะช่วยลดจำนวนเซิร์ฟเวอร์ที่ต้องการ เนื่องจากเซิร์ฟเวอร์หนึ่งสามารถรันหลายเธรดพร้อมกันได้

40. วงจรชีวิตของเธรดมีสถานะอะไรบ้าง?

คำถามและคำตอบสัมภาษณ์ Java Core 50 อันดับแรก  ส่วนที่ 3 - 3
  1. ใหม่:ในสถานะนี้ คลาสอ็อบเจ็กต์Threadจะถูกสร้างขึ้นโดยใช้ตัวดำเนินการใหม่ แต่ไม่มีเธรดอยู่ เธรดจะไม่เริ่มจนกว่าเราจะเรียกไฟล์start().
  2. รันได้:ในสถานะนี้ เธรดพร้อมที่จะรันหลังจากเรียกใช้เมธอด เริ่ม(). อย่างไรก็ตาม ตัวกำหนดเวลาเธรดยังไม่ถูกเลือก
  3. กำลังทำงาน:ในสถานะนี้ ตัวกำหนดเวลาเธรดจะเลือกเธรดจากสถานะพร้อมและรัน
  4. กำลังรอ/ถูกบล็อก:ในสถานะนี้ เธรดไม่ทำงานแต่ยังคงอยู่หรือกำลังรอให้เธรดอื่นดำเนินการให้เสร็จสิ้น
  5. ตาย/สิ้นสุด:เมื่อออกจากวิธีการrun()เธรดจะอยู่ในสถานะสิ้นสุดหรือสิ้นสุด

41. เป็นไปได้ไหมที่จะเริ่มกระทู้สองครั้ง?

ไม่ เราไม่สามารถรีสตาร์ทเธรดได้ เนื่องจากเมื่อเธรดเริ่มต้นและดำเนินการแล้ว เธรดจะเข้าสู่สถานะไม่ทำงาน ดังนั้นหากเราพยายามรันเธรดสองครั้ง มันจะส่ง runtimeException " java.lang.IllegalThreadStateException " มาดูกัน:
class DoubleStartThreadExample extends Thread {

   /**
    * Имитируем работу треда
    */
   public void run() {
	// что-то происходит. Для нас не существенно на этом этапе
   }

   /**
    * Запускаем тред дважды
    */
   public static void main(String[] args) {
       DoubleStartThreadExample doubleStartThreadExample = new DoubleStartThreadExample();
       doubleStartThreadExample.start();
       doubleStartThreadExample.start();
   }
}
ทันทีที่งานถึงจุดเริ่มต้นที่สองของเธรดเดียวกัน ก็จะมีข้อยกเว้น ลองด้วยตัวเอง ;) เห็นครั้งเดียวดีกว่าได้ยินร้อยครั้ง

42. จะเป็นอย่างไรถ้าคุณเรียกใช้เมธอด run() โดยตรงโดยไม่ต้องเรียกใช้เมธอด start()?

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

       // просто будут вызваны в потоке main два метода, один за другим.
       runExample1.run();
       runExample2.run();
   }
}
และผลลัพธ์ไปยังคอนโซลจะเป็นดังนี้:

0123401234
จะเห็นได้ว่าไม่มีการสร้างเธรด ทุกอย่างทำงานเหมือนชั้นเรียนปกติ ขั้นแรกวิธีคลาสแรกได้ผล จากนั้นวิธีที่สอง

43. เธรดเดมอนคืออะไร?

คำถามและคำตอบสัมภาษณ์ Java Core 50 อันดับแรก  ส่วนที่ 3 - 4Daemon thread (ต่อไปนี้จะเรียกว่า daemon thread) เป็นเธรดที่ทำงานเบื้องหลังโดยสัมพันธ์กับเธรดอื่น นั่นคือหน้าที่ของมันคือดำเนินงานเสริมที่ต้องทำร่วมกับเธรดอื่น (หลัก) เท่านั้น มีเธรด daemon มากมายที่ทำงานโดยอัตโนมัติ เช่น Garbage Collector, Finalizer เป็นต้น

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

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

วิธีการทำงานในคลาส Thread

คลาสjava.lang.Threadมีสองวิธีในการทำงานกับ thread daemon:
  1. public void setDaemon(boolean status)- ระบุว่านี่จะเป็นเธรด daemon ค่าดีฟอลต์คือfalseซึ่งหมายความว่าเธรดที่ไม่ใช่ daemon จะถูกสร้างขึ้น เว้นแต่จะระบุไว้แยกต่างหาก
  2. public boolean isDaemon()- โดยพื้นฐานแล้ว นี่คือ getter สำหรับตัวแปรdaemonที่เราตั้งค่าโดยใช้วิธีก่อนหน้า
ตัวอย่าง:
class DaemonThreadExample extends Thread {

   public void run() {
       // Проверяет, демон ли этот поток or нет
       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();

       // теперь thread1 - поток-демон.
       thread1.setDaemon(true);

       System.out.println("демон?.. " + thread1.isDaemon());
       System.out.println("демон?.. " + thread2.isDaemon());
       System.out.println("демон?.. " + thread3.isDaemon());

       thread1.start();
       thread2.start();
       thread3.start();
   }
}
เอาต์พุตคอนโซล:

демон?.. true
демон?.. false
демон?.. false
daemon thread
user thread
user thread
จากผลลัพธ์ เราจะเห็นว่าภายในเธรดนั้นเอง โดยใช้currentThread()วิธีคงที่ เราสามารถค้นหาได้ว่าเธรดใดอยู่ในมือข้างหนึ่ง ในทางกลับกัน ถ้าเราอ้างอิงถึงวัตถุของเธรดนี้ เราก็สามารถค้นหาได้ โดยตรงจากมัน สิ่งนี้ให้ความยืดหยุ่นที่จำเป็นในการกำหนดค่า

44. เป็นไปได้ไหมที่จะสร้างเธรดเป็น 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();

       // здесь будет выброшено исключение
       afterStartExample.setDaemon(true);
   }
}
เอาต์พุตคอนโซล:

Working...
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.lang.Thread.setDaemon(Thread.java:1359)
	at SetDaemonAfterStartExample.main(SetDaemonAfterStartExample.java:14)

45. การปิดเครื่องคืออะไร?

Shutdownhookเป็นเธรดที่ถูกเรียกโดยปริยายก่อนที่ JVM (Java Virtual Machine) จะปิดตัวลง ดังนั้นเราจึงสามารถใช้เพื่อล้างทรัพยากรหรือบันทึกสถานะเมื่อ Java Virtual Machine ปิดตัวลงตามปกติหรือกะทันหัน เราสามารถเพิ่มได้shutdown hookโดยใช้วิธีการต่อไปนี้:
Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());
ดังแสดงในตัวอย่าง:
/**
* Программа, которая показывает How запустить shutdown hook тред,
* который выполнится аккурат до окончания работы JVM
*/
class ShutdownHookThreadExample extends Thread {

   public void run() {
       System.out.println("shutdown hook задачу выполнил");
   }

   public static void main(String[] args) {

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

       System.out.println("Теперь программа засыпает, нажмите ctrl+c чтоб завершить ее.");
       try {
           Thread.sleep(60000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
}
เอาต์พุตคอนโซล:

Теперь программа засыпает, нажмите ctrl+c чтоб завершить ее.
shutdown hook задачу выполнил

46. ​​​​การซิงโครไนซ์คืออะไร?

การซิงโครไนซ์ใน Java คือความสามารถในการควบคุมการเข้าถึงทรัพยากรที่ใช้ร่วมกันของหลายเธรด เมื่อหลายเธรดพยายามทำงานเดียวกัน อาจมีโอกาสเกิดผลลัพธ์ที่ผิดพลาด ดังนั้นเพื่อแก้ไขปัญหานี้ Java จึงใช้การซิงโครไนซ์ เนื่องจากมีเธรดเดียวเท่านั้นที่สามารถทำงานได้ในแต่ละครั้ง การซิงโครไนซ์สามารถทำได้สามวิธี:
  • วิธีการซิงโครไนซ์
  • โดยการซิงโครไนซ์บล็อกเฉพาะ
  • การซิงโครไนซ์แบบคงที่

การซิงโครไนซ์วิธีการ

วิธีการซิงโครไนซ์ใช้เพื่อล็อควัตถุสำหรับทรัพยากรที่ใช้ร่วมกัน เมื่อเธรดเรียกวิธีการซิงโครไนซ์ เธรดนั้นจะได้รับการล็อคบนวัตถุนั้นโดยอัตโนมัติ และปล่อยเมื่อเธรดทำงานเสร็จสิ้น เพื่อให้ได้ผล คุณต้องเพิ่ม คำหลัก ที่ซิงโค รไนซ์ เรามาดูกันว่าสิ่งนี้ทำงานอย่างไรด้วยตัวอย่าง:
/**
* Пример, где мы синхронизируем метод. То есть добавляем ему слово synchronized.
* Есть два писателя, которые хотят использовать один принтер. Они подготовor свои поэмы
* И конечно же не хотят, чтоб их поэмы перемешались, а хотят, чтоб работа была сделана по * * * очереди для каждого из них
*/
class Printer {

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

   public static void main(String args[]) {
       // один an object для двух тредов
       Printer printer  = new Printer();

       // создаем два треда
       Writer1 writer1 = new Writer1(printer);
       Writer2 writer2 = new Writer2(printer);

       // запускаем их
       writer1.start();
       writer2.start();
   }
}

/**
* Писатель номер 1, который пишет свою поэму.
*/
class Writer1 extends Thread {
   Printer printer;

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

   public void run() {
       List<string> poem = Arrays.asList("Я ", this.getName(), " Пишу", " Письмо");
       printer.print(poem);
   }

}

/**
* Писатель номер 2, который пишет свою поэму.
*/
class Writer2 extends Thread {
   Printer printer;

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

   public void run() {
       List<String> poem = Arrays.asList("Не Я ", this.getName(), " Не пишу", " Не Письмо");
       printer.print(poem);
   }
}
และเอาต์พุตไปยังคอนโซล:

Я Thread-0 Пишу Письмо
Не Я Thread-1 Не пишу Не Письмо

บล็อกการซิงโครไนซ์

บล็อกที่ซิงโครไนซ์สามารถใช้เพื่อดำเนินการซิงโครไนซ์กับทรัพยากรวิธีการเฉพาะใดๆ สมมติว่าด้วยวิธีการขนาดใหญ่ (ใช่ ใช่ คุณไม่สามารถเขียนสิ่งเหล่านี้ได้ แต่บางครั้งก็เกิดขึ้น) คุณต้องซิงโครไนซ์เพียงส่วนเล็ก ๆ ด้วยเหตุผลบางประการ หากคุณใส่รหัสทั้งหมดของวิธีการลงในบล็อกที่ซิงโครไนซ์ มันจะทำงานเหมือนกับวิธีการซิงโครไนซ์ ไวยากรณ์มีลักษณะดังนี้:
synchronized (“an object для блокировки”) {
   // сам code, который нужно защитить
}
เพื่อไม่ให้ทำซ้ำตัวอย่างก่อนหน้านี้ เราจะสร้างเธรดผ่านคลาสที่ไม่ระบุชื่อ - นั่นคือการนำอินเทอร์เฟซ Runnable ไปใช้งานทันที
/**
* Вот How добавляется блок синхронизации.
* Внутри нужно указать у кого будет взят мьютекс для блокировки.
*/
class Printer {

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

   public static void main(String args[]) {
       // один an object для двух тредов
       Printer printer = new Printer();

       // создаем два треда
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Я ", "Writer1", " Пишу", " Письмо");
               printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Не Я ", "Writer2", " Не пишу", " Не Письмо");
               printer.print(poem);
           }
       });

       // запускаем их
       writer1.start();
       writer2.start();
   }
}

}
และส่งออกไปยังคอนโซล

Я Writer1 Пишу Письмо
Не Я Writer2 Не пишу Не Письмо

การซิงโครไนซ์แบบคงที่

หากคุณทำให้วิธีการซิงโครไนซ์แบบคงที่ การล็อคจะอยู่ที่คลาส ไม่ใช่บนอ็อบเจ็กต์ ในตัวอย่างนี้ เราใช้คำสำคัญที่ซิงโครไนซ์กับวิธีคงที่เพื่อดำเนินการซิงโครไนซ์แบบคงที่:
/**
* Вот How добавляется блок синхронизации.
* Внутри нужно указать у кого будет взят мьютекс для блокировки.
*/
class Printer {

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

   public static void main(String args[]) {

       // создаем два треда
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Я ", "Writer1", " Пишу", " Письмо");
               Printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Не Я ", "Writer2", " Не пишу", " Не Письмо");
               Printer.print(poem);
           }
       });

       // запускаем их
       writer1.start();
       writer2.start();
   }
}
และเอาต์พุตไปยังคอนโซล:

Не Я Writer2 Не пишу Не Письмо
Я Writer1 Пишу Письмо

47. ตัวแปรผันผวนคืออะไร?

คีย์เวิร์ดvolatileถูกใช้ในการเขียนโปรแกรมแบบมัลติเธรดเพื่อความปลอดภัยของเธรด เนื่องจากการแก้ไขตัวแปรที่ไม่แน่นอนหนึ่งรายการสามารถมองเห็นได้ในเธรดอื่นทั้งหมด ดังนั้น ตัวแปรหนึ่งรายการสามารถใช้ได้ทีละเธรด การใช้คีย์เวิร์ดvolatileคุณสามารถรับประกันได้ว่าตัวแปรจะปลอดภัยสำหรับเธรด และจะถูกจัดเก็บไว้ในหน่วยความจำที่ใช้ร่วมกัน และเธรดจะไม่นำตัวแปรนั้นไปไว้ในแคช มันดูเหมือนอะไร?
private volatile AtomicInteger count;
volatileเราเพียงแค่เพิ่ม เข้าไป ในตัวแปร แต่นี่ไม่ได้หมายถึงความปลอดภัยของเธรดโดยสมบูรณ์... ท้ายที่สุดแล้ว การดำเนินการอาจไม่เป็นแบบอะตอมมิกบนตัวแปร แต่คุณสามารถใช้Atomicคลาสที่ดำเนินการแบบอะตอมมิกได้ นั่นคือในการดำเนินการครั้งเดียวโดยโปรเซสเซอร์ java.util.concurrent.atomicคลาสดังกล่าวจำนวนมาก สามารถ พบได้ในแพ็คเกจ

48. การหยุดชะงักคืออะไร

Deadlock ใน Java เป็นส่วนหนึ่งของมัลติเธรด การชะงักงันอาจเกิดขึ้นได้ในสถานการณ์ที่เธรดกำลังรอการล็อกวัตถุที่ได้รับจากเธรดอื่น และเธรดที่สองกำลังรอการล็อกวัตถุที่ได้รับจากเธรดแรก ดังนั้นทั้งสองเธรดจึงรอกันและกันและจะไม่ดำเนินการโค้ดต่อไป คำถามและคำตอบสัมภาษณ์ Java Core 50 อันดับแรก  ส่วนที่ 3 - 5ลองดูตัวอย่างที่มีคลาสที่ใช้ 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();
   }
}

/**
* Класс, который принимает два ресурса.
*/
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() + " захватил ресурс: " + r1);

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

           synchronized (r2) {
               System.out.println(Thread.currentThread().getName() + " захватил ресурс: " + r2);
           }
       }
   }
}
เอาต์พุตคอนโซล:

Первый тред захватил первый ресурс
Второй тред захватывает второй ресурс

49. จะหลีกเลี่ยงการหยุดชะงักได้อย่างไร?

จากสิ่งที่เรารู้ว่าการหยุดชะงักเกิดขึ้นได้อย่างไร เราสามารถสรุปได้...
  • ดังที่แสดงในตัวอย่างข้างต้น การหยุดชะงักเกิดจากการซ้อนตัวล็อค นั่นคือภายในล็อคหนึ่งจะมีอีกล็อคหนึ่งหรือมากกว่านั้น สิ่งนี้สามารถหลีกเลี่ยงได้ด้วยวิธีต่อไปนี้ - แทนที่จะซ้อนกัน คุณต้องเพิ่มสิ่งที่เป็นนามธรรมใหม่ด้านบนและให้การล็อคในระดับที่สูงกว่า และลบการล็อคที่ซ้อนกัน
  • ยิ่งมีการปิดกั้นมากเท่าใด โอกาสที่จะเกิดการหยุดชะงักก็มีมากขึ้นเท่านั้น ดังนั้นทุกครั้งที่คุณเพิ่มล็อค คุณต้องพิจารณาว่าจำเป็นจริงๆ หรือไม่ และจะสามารถหลีกเลี่ยงการเพิ่มล็อคใหม่ได้หรือไม่
  • การใช้งานThread.join(). การหยุดชะงักสามารถทำได้เมื่อเธรดหนึ่งกำลังรอเธรดอื่นอยู่ เพื่อหลีกเลี่ยงปัญหานี้ คุณอาจพิจารณาตั้งค่าขีดจำกัดเวลาเกี่ยวกับjoin()วิธีการนี้
  • ถ้าเรามีเธรดเดียวก็จะไม่มีการหยุดชะงัก ;)

50. สภาพการแข่งขันคืออะไร?

หากในการแข่งขันจริง รถยนต์ต่างๆ จะดำเนินการ เธรดก็จะดำเนินการในการแข่งขันด้วยคำศัพท์เฉพาะทางการแข่งขันแบบมัลติเธรด แต่ทำไม? มีสองเธรดที่กำลังทำงานอยู่และสามารถเข้าถึงออบเจ็กต์เดียวกันได้ และพวกเขาสามารถลองอัปเดตสถานะได้ในเวลาเดียวกัน จนถึงตอนนี้ทุกอย่างชัดเจนใช่ไหม? ดังนั้นเธรดจึงทำงานแบบขนานจริง (หากมีมากกว่าหนึ่งคอร์ในโปรเซสเซอร์) หรือแบบขนานตามเงื่อนไขเมื่อโปรเซสเซอร์จัดสรรช่วงเวลาสั้น ๆ และเราไม่สามารถควบคุมกระบวนการเหล่านี้ได้ ดังนั้นเราจึงไม่สามารถรับประกันได้ว่าเมื่อเธรดหนึ่งอ่านข้อมูลจากออบเจ็กต์ จะมีเวลาในการเปลี่ยนแปลงก่อนที่เธรดอื่นจะทำ ปัญหาเช่นนี้เกิดขึ้นเมื่อชุดค่าผสมการทดสอบและดำเนินการผ่านไป มันหมายความว่าอะไร? ตัวอย่างเช่น เรามีifนิพจน์ในเนื้อความที่เงื่อนไขเปลี่ยนแปลงไป นั่นคือ:
int z = 0;

// проверь
if (z < 5) {
//действуй
   z = z + 5;
}
ดังนั้นอาจมีสถานการณ์ที่เธรดสองเธรดเข้าสู่บล็อกโค้ดนี้พร้อมกันในเวลาที่ z ยังคงเท่ากับศูนย์และร่วมกันเปลี่ยนค่านี้ และท้ายที่สุดเราจะไม่ได้รับค่าที่คาดหวังเป็น 5 แต่เป็น 10 จะหลีกเลี่ยงสิ่งนี้ได้อย่างไร? คุณต้องล็อคก่อนและหลังการดำเนินการ นั่นคือสำหรับเธรดแรกที่จะเข้าสู่บล็อกifให้ดำเนินการทั้งหมด เปลี่ยนแปลง จากนั้นzให้โอกาสเธรดถัดไปทำสิ่งนี้เท่านั้น แต่เธรดถัดไปจะไม่เข้าสู่บล็อกifเนื่องจากzจะเท่ากับ 5 อยู่แล้ว:
// получить блокировку для z
if (z < 5) {
   z = z + 5;
}
// выпустить из блокировки z
===================================================

แทนที่จะเป็นเอาท์พุต

ฉันอยากจะบอกว่าขอบคุณทุกคนที่อ่านจนจบ มันเป็นการเดินทางที่ยาวนานและคุณก็ทำมันได้! ทุกอย่างอาจไม่ชัดเจน นี่เป็นเรื่องปกติ ทันทีที่ฉันเริ่มเรียนรู้ Java ฉันไม่สามารถคาดเดาได้ว่าตัวแปรคงที่คืออะไร แต่ไม่มีอะไร ฉันนอนกับความคิดนี้ อ่านแหล่งข้อมูลเพิ่มเติมสองสามแห่งแล้วก็เข้าใจในที่สุด การเตรียมตัวสำหรับการสัมภาษณ์ถือเป็นเรื่องทางวิชาการมากกว่าภาคปฏิบัติ ดังนั้นก่อนการสัมภาษณ์แต่ละครั้ง คุณจะต้องทบทวนและทบทวนความจำเกี่ยวกับสิ่งที่อาจไม่ได้ใช้บ่อยนัก

และเช่นเคย ลิงค์ที่เป็นประโยชน์:

ขอบคุณทุกท่านที่อ่าน แล้วพบกันใหม่) โปรไฟล์ของฉันบน GitHub
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION