ในอดีต Java พยายามจัดให้มีการขัดจังหวะแบบจำกัดล่วงหน้า แต่มีปัญหามากมาย เช่น Thread.stop, Thread.suspend และ Thread.resume ที่ละทิ้งไปก่อนหน้านี้ ในทางกลับกัน เมื่อคำนึงถึงความทนทานของโค้ดแอปพลิเคชัน Java เกณฑ์การเขียนโปรแกรมจะลดลง และความน่าจะเป็นของโปรแกรมเมอร์ที่ไม่ทราบกลไกพื้นฐานที่สร้างความเสียหายให้กับระบบโดยไม่ได้ตั้งใจจะลดลง
ปัจจุบัน การตั้งเวลาเธรดของ Java ไม่ได้จัดให้มีการขัดจังหวะแบบยึดเอาเสียก่อน แต่ใช้การขัดจังหวะแบบร่วมมือ ในความเป็นจริง หลักการของการหยุดชะงักของความร่วมมือนั้นง่ายมาก ซึ่งก็คือการสำรวจเครื่องหมายที่ระบุถึงการหยุดชะงัก เราสามารถนำไปใช้ในโค้ดทั่วไปได้
ตัวอย่างเช่นรหัสต่อไปนี้:
บูลระเหยถูกขัดจังหวะ;
-
ในขณะที่(!ถูกขัดจังหวะ) {
คำนวณ();
-
อย่างไรก็ตาม ปัญหาโค้ดข้างต้นก็ชัดเจนเช่นกัน เมื่อเวลาประมวลผลค่อนข้างนาน การขัดจังหวะจะไม่สามารถตอบสนองได้ทันเวลา ในทางกลับกัน ด้วยการใช้การโพลเพื่อตรวจสอบตัวแปรแฟล็ก ไม่มีทางที่จะขัดจังหวะการดำเนินการบล็อกเธรด เช่น การรอและโหมดสลีป
หากคุณยังคงใช้แนวคิดข้างต้น หากคุณต้องการให้การขัดจังหวะตอบสนองในเวลาที่เหมาะสม คุณต้องตรวจสอบตัวแปรเครื่องหมายผ่านการตั้งเวลาเธรดที่ด้านล่างของเครื่องเสมือน ใช่ สิ่งนี้เสร็จสิ้นใน JVM แล้ว
ต่อไปนี้คัดลอกมาจากซอร์สโค้ดของ java.lang.Thread:
บูลีนคงที่สาธารณะถูกขัดจังหวะ () {
กลับ currentThread().isInterrupted (จริง);
-
-
บูลีนพื้นเมืองส่วนตัวถูกขัดจังหวะ (บูลีน ClearInterrupted);
พบว่า isInterrupted ได้รับการประกาศเป็นวิธีการดั้งเดิม ซึ่งขึ้นอยู่กับการใช้งานพื้นฐานของ JVM
ในความเป็นจริง JVM จะรักษาการตั้งค่าสถานะการขัดจังหวะภายในสำหรับแต่ละเธรด อย่างไรก็ตาม แอปพลิเคชันไม่สามารถเข้าถึงตัวแปรขัดจังหวะนี้ได้โดยตรง และต้องดำเนินการผ่านวิธีการต่อไปนี้:
หัวข้อชั้นเรียนสาธารณะ {
//ตั้งค่าเครื่องหมายขัดจังหวะ
โมฆะสาธารณะขัดจังหวะ () { ... }
// รับค่าของเครื่องหมายขัดจังหวะ
บูลีนสาธารณะ isInterrupted() { ... }
//ล้างเครื่องหมายขัดจังหวะและส่งกลับค่าของเครื่องหมายขัดจังหวะสุดท้าย
บูลีนคงที่สาธารณะถูกขัดจังหวะ () { ... }
-
โดยปกติแล้ว การเรียกเมธอดขัดจังหวะของเธรดจะไม่ทำให้เกิดการขัดจังหวะในทันที แต่จะตั้งค่าสถานะขัดจังหวะภายใน JVM เท่านั้น ดังนั้น โดยการตรวจสอบแฟล็กขัดจังหวะ แอปพลิเคชันสามารถทำสิ่งพิเศษหรือละเว้นการขัดจังหวะได้อย่างสมบูรณ์
คุณอาจคิดว่าหาก JVM จัดเตรียมกลไกการขัดจังหวะแบบคร่าวๆ เท่านั้น โดยพื้นฐานแล้วไม่มีข้อได้เปรียบใดๆ เลยเมื่อเทียบกับวิธีการกำหนดตัวแปรขัดจังหวะและการสำรวจของแอปพลิเคชันเอง
ข้อได้เปรียบหลักของตัวแปรขัดจังหวะภายในของ JVM คือ มีกลไกในการจำลอง "กับดักขัดจังหวะ" อัตโนมัติสำหรับบางสถานการณ์
เมื่อดำเนินการบล็อกการโทรที่เกี่ยวข้องกับการกำหนดเวลาเธรด (เช่น รอ สลีป และเข้าร่วม) หากเกิดการขัดจังหวะ เธรดที่ถูกบล็อกจะส่ง InterruptedException "โดยเร็วที่สุด" ดังนั้นเราจึงสามารถใช้กรอบโค้ดต่อไปนี้เพื่อจัดการกับการขัดจังหวะการบล็อกเธรด:
พยายาม {
//รอ นอน หรือเข้าร่วม
-
จับ (InterruptedException e) {
// งานจัดการขัดจังหวะบางอย่าง
-
"เร็วที่สุด" ฉันเดาว่า JVM จะตรวจสอบตัวแปรขัดจังหวะในช่องว่างระหว่างการตั้งเวลาเธรด ความเร็วขึ้นอยู่กับการใช้งาน JVM และประสิทธิภาพของฮาร์ดแวร์
อย่างไรก็ตาม สำหรับการดำเนินการบล็อกเธรดบางอย่าง JVM จะไม่ส่ง InterruptedException โดยอัตโนมัติ ตัวอย่างเช่น การดำเนินการ I/O บางอย่าง และการดำเนินการล็อคภายใน สำหรับการดำเนินการประเภทนี้ การขัดจังหวะสามารถจำลองได้ด้วยวิธีอื่น:
1) ซ็อกเก็ต I/O แบบอะซิงโครนัสใน java.io
เมื่ออ่านและเขียนซ็อกเก็ต วิธีการอ่านและเขียนของ InputStream และ OutputStream จะบล็อกและรอ แต่จะไม่ตอบสนองต่อการขัดจังหวะของ Java อย่างไรก็ตาม หลังจากเรียกเมธอดการปิดของ Socket แล้ว เธรดที่ถูกบล็อกจะส่ง SocketException
2) Asynchronous I/O ใช้งานโดยใช้ Selector
หากเธรดถูกบล็อกใน Selector.select (ใน java.nio.channels) การเรียกใช้เมธอด wakeup จะทำให้เกิดข้อยกเว้น ClosedSelectorException
3) ล็อคการเข้าซื้อกิจการ
หากเธรดกำลังรอรับการล็อคภายใน เราไม่สามารถขัดจังหวะเธรดได้ อย่างไรก็ตาม การใช้เมธอด lockInterruptually ของคลาส Lock ทำให้เราสามารถจัดเตรียมความสามารถในการขัดจังหวะในขณะที่รอการล็อคได้
นอกจากนี้ ในเฟรมเวิร์กที่งานและเธรดถูกแยกออกจากกัน งานมักจะไม่รู้ว่าเธรดใดจะถูกเรียกโดย และดังนั้นจึงไม่ทราบกลยุทธ์ของการเรียกเธรดในการจัดการกับการขัดจังหวะ ดังนั้น หลังจากที่งานตั้งค่าสถานะการหยุดชะงักของเธรดแล้ว ไม่มีการรับประกันว่างานจะถูกยกเลิก ดังนั้นจึงมีหลักการเขียนโปรแกรมสองประการ:
1) คุณไม่ควรขัดจังหวะเธรด เว้นแต่คุณจะทราบนโยบายการขัดจังหวะของเธรด
หลักการนี้บอกเราว่าเราไม่ควรเรียกวิธีการขัดจังหวะของเธรดในเฟรมเวิร์กเช่น Executer โดยตรง แต่ควรใช้วิธีเช่น Future.cancel เพื่อยกเลิกงาน
2) รหัสงานไม่ควรเดาว่าการขัดจังหวะหมายถึงอะไรต่อเธรดการดำเนินการ
หลักการนี้บอกเราว่าเมื่อโค้ดทั่วไปพบกับ InterruptedException มันไม่ควรจับมันและ "กลืนมัน" แต่ควรส่งต่อไปยังโค้ดบนสุด
กล่าวโดยสรุป กลไกการขัดจังหวะแบบไม่ยึดเอาเสียก่อนใน Java กำหนดให้เราต้องเปลี่ยนแนวคิดการขัดจังหวะแบบยึดเสียก่อนแบบดั้งเดิม และใช้หลักการและรูปแบบที่สอดคล้องกันสำหรับการเขียนโปรแกรมโดยอาศัยความเข้าใจแก่นแท้ของมัน