คำถามและคำตอบสัมภาษณ์งาน 50 อันดับแรกสำหรับ Java Core ส่วนที่ 1
มัลติเธรด
24. ฉันจะสร้างเธรดใหม่ใน Java ได้อย่างไร
ไม่ทางใดก็ทางหนึ่งเธรดถูกสร้างขึ้นโดยใช้คลาสเธรด แต่มีหลายวิธีในการทำเช่นนี้ ...- สืบทอดjava.lang.Thread _
- ใช้ อินเทอร์เฟซ 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. ความแตกต่างระหว่างกระบวนการและเธรดคืออะไร?
กระบวนการและเธรดแตกต่างกันด้วยวิธีต่อไปนี้:- โปรแกรมที่ทำงานอยู่เรียกว่ากระบวนการ แต่เธรดเป็นส่วนหนึ่งของกระบวนการ
- กระบวนการเป็นอิสระ แต่เธรดเป็นส่วนหนึ่งของกระบวนการ
- กระบวนการมีพื้นที่แอดเดรสที่แตกต่างกันในหน่วยความจำ แต่เธรดใช้พื้นที่แอดเดรสร่วมกัน
- การสลับบริบทระหว่างเธรดนั้นเร็วกว่าการสลับระหว่างกระบวนการ
- การสื่อสารระหว่างกระบวนการนั้นช้าและมีราคาแพงกว่าการสื่อสารระหว่างเธรด
- การเปลี่ยนแปลงใด ๆ ในกระบวนการพาเรนต์จะไม่ส่งผลต่อกระบวนการลูก แต่การเปลี่ยนแปลงในเธรดหลักอาจส่งผลต่อเธรดลูก
26. มัลติเธรดมีประโยชน์อย่างไร?
- มัลติเธรดช่วยให้แอปพลิเคชัน/โปรแกรมตอบสนองต่ออินพุตได้เสมอ แม้ว่าจะรันงานเบื้องหลังบางอย่างอยู่แล้วก็ตาม
- มัลติเธรดทำให้งานเสร็จเร็วขึ้น เนื่องจากเธรดทำงานแยกกัน
- มัลติเธรดช่วยให้ใช้หน่วยความจำแคชได้ดีขึ้น เนื่องจากเธรดสามารถเข้าถึงทรัพยากรหน่วยความจำที่ใช้ร่วมกันได้
- มัลติเธรดช่วยลดจำนวนเซิร์ฟเวอร์ที่ต้องการ เนื่องจากเซิร์ฟเวอร์หนึ่งเครื่องสามารถรันหลายเธรดพร้อมกันได้
27. สถานะในวงจรชีวิตของเธรดคืออะไร?
- ใหม่:ในสถานะนี้ วัตถุ เธรดถูกสร้างขึ้นโดยใช้ตัวดำเนินการใหม่ แต่ยังไม่มีเธรดใหม่ เธรดจะไม่เริ่มต้นจนกว่าเราจะเรียกใช้เมธอดstart()
- Runnable:ในสถานะนี้ เธรดพร้อมที่จะทำงานหลังจาก start()
เรียกว่าเมธอด อย่างไรก็ตาม ยังไม่ได้เลือกโดยตัวกำหนดตารางเวลาของเธรด - กำลังทำงาน:ในสถานะนี้ ตัวกำหนดตารางเวลาเธรดจะเลือกเธรดจากสถานะพร้อม และรัน
- กำลังรอ/ถูกบล็อก:ในสถานะนี้ เธรดไม่ทำงาน แต่ยังคงอยู่หรือรอให้เธรดอื่นทำงานให้เสร็จ
- ตาย/สิ้นสุด:เมื่อเธรดออกจาก เมธอด 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:- โมฆะสาธารณะ setDaemon (สถานะบูลีน) — วิธีการนี้ระบุว่านี่จะเป็นเธรดภูตหรือไม่ ค่าดีฟอลต์เป็นเท็จ ซึ่งหมายความว่าจะไม่มีการสร้างเธรด daemon เว้นแต่คุณจะระบุไว้อย่างชัดเจน
- บูลีนสาธารณะ 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 การหยุดชะงักเป็นสิ่งที่สามารถเกิดขึ้นได้โดยเป็นส่วนหนึ่งของมัลติเธรด การล็อกตายสามารถเกิดขึ้นได้เมื่อเธรดกำลังรอการล็อกของอ็อบเจ็กต์ที่ได้รับจากเธรดอื่น และเธรดที่สองกำลังรอการล็อกของอ็อบเจ็กต์ที่ได้รับจากเธรดแรก ซึ่งหมายความว่าทั้งสองเธรดกำลังรอซึ่งกันและกัน และการเรียกใช้โค้ดไม่สามารถดำเนินต่อไปได้ ลองพิจารณาตัวอย่างที่มีคลาสที่ใช้ 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 เป็นครั้งแรก ฉันคิดไม่ออกว่าตัวแปรสแตติกคืออะไร แต่ไม่ใช่เรื่องใหญ่ ฉันนอนบนนั้น อ่านอีกสองสามแหล่ง แล้วความเข้าใจก็มาถึง การเตรียมตัวสำหรับการสัมภาษณ์เป็นคำถามเชิงวิชาการมากกว่าคำถามเชิงปฏิบัติ ด้วยเหตุนี้ ก่อนการสัมภาษณ์แต่ละครั้ง คุณควรทบทวนและรีเฟรชในความทรงจำของคุณเกี่ยวกับสิ่งที่คุณอาจไม่ได้ใช้บ่อยนักและเช่นเคย ต่อไปนี้เป็นลิงก์ที่มีประโยชน์:
- ข้อยกเว้นใน Java
- วิธีการเริ่มต้นใน Java
- ติดตามฉันบน GitHub ฉันวางโครงการการศึกษาทั้งหมดไว้ที่นั่น ทุกสิ่งที่ฉันสอน ตัวอย่างเช่น ฉันเพิ่งขุดเข้าไปในDrools RuleEngine
- ตรวจสอบข้อยกเว้นที่ไม่ได้ตรวจสอบ
- ลองกับทรัพยากร
- คำหลักสุดท้าย
- ขณะที่ฉัน กำลังเตรียมบทความนี้ ฉันชอบเว็บไซต์https://www.geeksforgeeks.org มาก มีข้อมูลเจ๋ง ๆ มากมายอยู่ที่นั่น
GO TO FULL VERSION