ในบทนี้เราจะแนะนำวิธีการรอ/ปลุกเธรด เนื้อหาที่เกี่ยวข้องรวมถึง:
1. บทนำสู่การรอ (), แจ้ง (), แจ้งเตือน () และวิธีการอื่น ๆ
2. รอ () และแจ้ง ()
3. รอ (หมดเวลานาน) และแจ้ง ()
4. รอ () และแจ้งเตือน ()
5. ทำไมการแจ้งเตือน () รอ () และฟังก์ชั่นอื่น ๆ ที่กำหนดไว้ในวัตถุไม่ใช่เธรด
รู้เบื้องต้นเกี่ยวกับการรอ (), แจ้ง (), NotifyAll () และวิธีการอื่น ๆ
ใน Object.java อินเทอร์เฟซเช่น WAIT (), Notify () และ NotifyAll () ถูกกำหนดไว้ ฟังก์ชั่นของการรอ () คือการให้เธรดปัจจุบันป้อนสถานะการรอและรอ () จะปล่อยให้เธรดปัจจุบันปล่อยล็อคไว้ บทบาทของการแจ้งเตือน () และ NotifyAll () คือการปลุกเธรดการรอคอยในวัตถุปัจจุบัน;
รายละเอียด API เกี่ยวกับการรอ/ปลุกในคลาสวัตถุมีดังนี้:
แจ้งเตือน () - ปลุกเธรดเดียวที่รออยู่บนจอภาพวัตถุนี้
NotifyAll () - ปลุกเธรดทั้งหมดที่รอการตรวจสอบวัตถุนี้
รอ () - ใส่เธรดปัจจุบันในสถานะ "รอ (บล็อก)" และ "จนกว่าเธรดอื่นจะเรียกวิธีการแจ้งเตือน () หรือวิธีการแจ้งเตือน () ของวัตถุนี้" และเธรดปัจจุบันถูกปลุก (ป้อนไปที่ " Steamed State ")
รอ (หมดเวลานาน) - ให้เธรดปัจจุบันอยู่ใน "สถานะรอ (บล็อก)" และ "จนกว่าเธรดอื่นจะเรียกวิธีการแจ้งเตือน () หรือวิธีการแจ้งเตือน () ของวัตถุนี้หรือเกินระยะเวลาที่ระบุ" และเธรดปัจจุบันถูกปลุก (ป้อน "พร้อม")
รอ (หมดเวลานาน, int nanos) - ใส่เธรดปัจจุบันใน "สถานะรอ (บล็อก)" จนกว่าเธรดอื่นจะเรียกวิธีการแจ้งเตือน () หรือ notifyall () ของวัตถุนี้หรือเธรดอื่น ๆ ขัดจังหวะเธรดปัจจุบัน หรือระยะเวลาที่แท้จริงเกินกว่า "และเธรดปัจจุบันถูกปลุก (ป้อนไปที่" พร้อมสถานะ ")
2. รอ () และแจ้ง () ตัวอย่าง
ต่อไปนี้เป็นตัวอย่างที่จะแสดงให้เห็นว่า "รอ () และแจ้ง () ร่วมกัน"
การคัดลอกรหัสมีดังนี้:
// waittest.java ซอร์ส
คลาส Threada ขยายเธรด {
Threada สาธารณะ (ชื่อสตริง) {
super (ชื่อ);
-
โมฆะสาธารณะเรียกใช้ () {
ซิงโครไนซ์ (นี่) {
System.out.println (thread.currentthread (). getName ()+"การโทรแจ้ง ()");
// ปลุกเธรดรอปัจจุบัน
แจ้ง();
-
-
-
ชั้นเรียนสาธารณะ waittest {
โมฆะคงที่สาธารณะหลัก (สตริง [] args) {
threada t1 = threada ใหม่ ("t1");
ซิงโครไนซ์ (T1) {
พยายาม {
// เริ่มต้น "เธรด t1"
System.out.println (thread.currentthread (). getName ()+"เริ่ม t1");
t1.start ();
// เธรดหลักรอให้ T1 ตื่นขึ้นมาผ่านการแจ้งเตือน ()
System.out.println (thread.currentthread (). getName ()+"รอ ()");
t1.wait ();
System.out.println (thread.currentthread (). getName ()+"ดำเนินการต่อ");
} catch (interruptedException e) {
E.PrintStackTrace ();
-
-
-
-
ผลการทำงาน:
การคัดลอกรหัสมีดังนี้:
เริ่มต้นหลัก T1
การรอหลัก ()
T1 Call Notify ()
หลักดำเนินการต่อ
ผลลัพธ์คำอธิบาย:
รูปต่อไปนี้แสดงการไหลของ "เธรดหลัก" และ "เธรด T1"
(01) โปรดทราบว่า "เธรดหลัก" ในรูปแสดงถึง "เธรดหลักหลัก" "Thread T1" หมายถึง "Thread T1" เริ่มต้นใน Waittest และ "ล็อค" หมายถึง "การล็อคแบบซิงโครนัสของวัตถุ T1"
(02) "เธรดหลัก" สร้าง "เธรด T1" ใหม่ผ่าน Threada ใหม่ ("T1") จากนั้น "ล็อคแบบซิงโครนัสของวัตถุ T1" จะได้รับผ่านการซิงโครไนซ์ (T1) จากนั้นโทร t1.start () เพื่อเริ่ม "เธรด t1"
(03) "เธรดหลัก" เรียกใช้งาน t1.wait () เพื่อปล่อย "ล็อคของวัตถุ T1" และเข้าสู่สถานะ "รอ (บล็อก)" รอเธรดบนวัตถุ T1 เพื่อปลุกผ่านการแจ้งเตือน () หรือแจ้งเตือน ()
(04) หลังจากเรียกใช้ "เธรด T1" "ล็อคของวัตถุปัจจุบัน" ได้รับผ่านการซิงโครไนซ์ (นี้); "เธรดหลัก"
(05) หลังจาก "เธรด T1" เสร็จสิ้นให้ปล่อย "ล็อคของวัตถุปัจจุบัน" หลังจากนั้นทันที "เธรดหลัก" จะได้รับ "ล็อคของวัตถุ T1" จากนั้นเรียกใช้
สำหรับรหัสข้างต้น? เพื่อนที่เคยถาม: t1.wait () ควรทำ "เธรด t1" แต่ทำไม "Main Thread หลัก" รอ?
ก่อนที่จะตอบคำถามนี้ลองมาดูย่อหน้าเกี่ยวกับการรอในเอกสาร JDK:
การคัดลอกรหัสมีดังนี้:
ทำให้เธรดปัจจุบันรอจนกว่าเธรดอื่นจะเรียกใช้วิธีการแจ้งเตือน () หรือวิธีการแจ้งเตือน () สำหรับวัตถุนี้
กล่าวอีกนัยหนึ่งวิธีนี้จะทำงานได้อย่างตรงไปตรงมาว่ามันเพียงแค่ดำเนินการรอสาย (0)
เธรดปัจจุบันจะต้องเป็นเจ้าของจอภาพของวัตถุนี้ สามารถเป็นเจ้าของใหม่ของการตรวจสอบและดำเนินการดำเนินการต่อ
ความหมายในภาษาจีนเป็นเรื่องประมาณ:
ทำให้ "เธรดปัจจุบัน" รอจนกว่าเธรดอื่นโทรแจ้ง () หรือแจ้งเตือน () เพื่อปลุกเธรด กล่าวอีกนัยหนึ่งวิธีนี้มีผลเช่นเดียวกับการรอ (0)! (เพิ่มเติมสำหรับวิธีการรอ (มิลลิส) เมื่อมิลลิสเป็น 0 มันหมายถึงการรอที่ไม่มีที่สิ้นสุดจนกว่าจะตื่นขึ้นมาโดยการแจ้งเตือน () หรือ notifyall ())
เมื่อการโทร "เธรดปัจจุบัน" รอ () จะต้องมีการล็อคการซิงโครไนซ์สำหรับวัตถุ หลังจากการโทรแบบเธรด () ล็อคจะถูกปล่อยออกมา; เธรดจากนั้นยังคงรอจนกว่าจะมีการตอบสนองต่อ "การซิงค์ล็อคสำหรับวัตถุนี้" จากนั้นก็สามารถทำงานต่อไปได้
หมายเหตุ: ในคำอธิบายของ JDK มีการกล่าวกันว่าฟังก์ชั่นของ WAIT () คือการรอ "เธรดปัจจุบัน" และ "เธรดปัจจุบัน" หมายถึงเธรดที่ทำงานบน CPU!
นอกจากนี้ยังหมายความว่าแม้ว่า t1.wait () เป็นวิธีการรอ () เรียกผ่าน "เธรด t1" สถานที่ที่ t1.wait () เรียกว่าอยู่ใน "Main Thread Main" เธรดหลักจะต้องเป็น "เธรดปัจจุบัน" นั่นคือสถานะการรันก่อนที่ T1.Wait () สามารถดำเนินการได้ ดังนั้น "เธรดปัจจุบัน" ในเวลานี้คือ "เธรดหลักหลัก"! ดังนั้น t1.wait () คือการทำ "เธรดหลัก" รอไม่ใช่ "เธรด t1"!
3. รอ (หมดเวลานาน) และแจ้ง ()
รอ (การหมดเวลานาน) จะใส่เธรดปัจจุบันใน "รอ (บล็อก) สถานะ" และ "จนกว่าเธรดอื่นจะเรียกวิธีการแจ้งเตือน () หรือ notifyall () วิธีการของวัตถุนี้หรือเกินจำนวนเวลาที่ระบุ" เธรดปัจจุบันถูกปลุก (ป้อน) "พร้อม")
ตัวอย่างต่อไปนี้แสดงให้เห็นถึงการหมดเวลาที่รอคอย (การหมดเวลานาน) และเธรดจะถูกปลุกขึ้นมา
การคัดลอกรหัสมีดังนี้:
// ซอร์สโค้ดของ waittimeouttest.java
คลาส Threada ขยายเธรด {
Threada สาธารณะ (ชื่อสตริง) {
super (ชื่อ);
-
โมฆะสาธารณะเรียกใช้ () {
System.out.println (thread.currentthread (). getName () + "เรียกใช้");
// วงจรอุบาทว์วิ่งอย่างต่อเนื่อง
ในขณะที่ (จริง)
-
-
ชั้นเรียนสาธารณะ waittimeouttest {
โมฆะคงที่สาธารณะหลัก (สตริง [] args) {
threada t1 = threada ใหม่ ("t1");
ซิงโครไนซ์ (T1) {
พยายาม {
// เริ่มต้น "เธรด t1"
System.out.println (thread.currentthread (). getName () + "เริ่ม t1");
t1.start ();
// เธรดหลักรอให้ T1 ตื่นขึ้นมาผ่านการแจ้งเตือน () หรือแจ้งเตือน () หรือล่าช้าเกิน 3000ms;
System.out.println (thread.currentthread (). getName () + "การโทรรอ");
T1. Wait (3000);
System.out.println (thread.currentthread (). getName () + "ดำเนินการต่อ");
} catch (interruptedException e) {
E.PrintStackTrace ();
-
-
-
-
ผลการทำงาน:
การคัดลอกรหัสมีดังนี้:
เริ่มต้นหลัก T1
รอสายหลักรอ
T1 run // หลังจากประมาณ 3 วินาที ... เอาท์พุท "Main Continue"
หลักดำเนินการต่อ
ผลลัพธ์คำอธิบาย:
รูปต่อไปนี้แสดงการไหลของ "เธรดหลัก" และ "เธรด T1"
(01) โปรดทราบว่า "เธรดหลัก" ในรูปหมายถึงเธรดหลัก WaittimeOuttest (เช่นเธรดหลัก) "Thread T1" หมายถึงเธรด T1 เริ่มต้นใน WaitTest และ "ล็อค" หมายถึง "การล็อคแบบซิงโครนัสของวัตถุ T1"
(02) เธรดหลักหลักเรียกใช้งาน t1.start () เพื่อเริ่ม "เธรด t1"
(03) เธรดหลักหลักดำเนินการ T1.Wait (3000) และในเวลานี้เธรดหลักจะเข้าสู่ "สถานะการบล็อก" มีความจำเป็นที่จะต้อง "เธรดที่ใช้สำหรับการล็อควัตถุ T1 เพื่อปลุกมันผ่านการแจ้งเตือน () หรือแจ้งเตือน ()" หรือ "หลังหมดเวลา 3000ms" เธรดหลักจะเข้าสู่ "สถานะพร้อม" จากนั้นก็สามารถทำงานได้
(04) หลังจาก "เธรด T1" กำลังทำงานอยู่มันจะเข้าสู่วนซ้ำที่ตายแล้วและยังคงทำงานต่อไป
(05) หลังจากหมดเวลาคือ 3000ms เธรดหลักจะเข้าสู่ "สถานะพร้อม" จากนั้นป้อน "สถานะการทำงาน"
4. รอ () และแจ้งเตือน ()
ผ่านตัวอย่างก่อนหน้านี้เรารู้ว่า Notify () สามารถปลุกเธรดเดียวที่รออยู่บนจอภาพวัตถุนี้
ด้านล่างเราแสดงให้เห็นถึงการใช้ NotifyAll () ผ่านตัวอย่าง
การคัดลอกรหัสมีดังนี้:
ชั้นเรียนสาธารณะแจ้งเตือน {
วัตถุคงที่ส่วนตัว OBJ = วัตถุใหม่ ();
โมฆะคงที่สาธารณะหลัก (สตริง [] args) {
threada t1 = threada ใหม่ ("t1");
threada t2 = threada ใหม่ ("t2");
threada t3 = threada ใหม่ ("t3");
t1.start ();
t2.start ();
t3.start ();
พยายาม {
System.out.println (thread.currentthread (). getName ()+"sleep (3000)");
Thread.sleep (3000);
} catch (interruptedException e) {
E.PrintStackTrace ();
-
ซิงโครไนซ์ (obj) {
// เธรดหลักกำลังรอการปลุก
System.out.println (thread.currentthread (). getName ()+"NotifyAll ()");
obj.notifyall ();
-
-
Threada คลาสคงที่ขยายเธรด {
Threada สาธารณะ (ชื่อสตริง) {
super (ชื่อ);
-
โมฆะสาธารณะเรียกใช้ () {
ซิงโครไนซ์ (obj) {
พยายาม {
// ผลการพิมพ์ผลลัพธ์
System.out.println (thread.currentthread (). getName () + "รอ");
// ปลุกเธรดรอปัจจุบัน
obj.wait ();
// ผลการพิมพ์ผลลัพธ์
System.out.println (thread.currentthread (). getName () + "ดำเนินการต่อ");
} catch (interruptedException e) {
E.PrintStackTrace ();
-
-
-
-
-
ผลการทำงาน:
การคัดลอกรหัสมีดังนี้:
T1 รอ
การนอนหลับหลัก (3000)
T3 รอ
T2 รอ
Main Notifyall ()
T2 ดำเนินการต่อ
T3 ดำเนินการต่อ
T1 ดำเนินการต่อ
ผลลัพธ์คำอธิบาย:
อ้างถึงผังงานด้านล่าง
(01) 3 เธรด "T1", "T2" และ "T3" ถูกสร้างขึ้นและเริ่มต้นในหัวข้อหลัก
(02) ด้ายหลักนอนเป็นเวลา 3 วินาทีผ่านการนอนหลับ (3000) ในระหว่างการนอนหลับของเธรดหลักเป็นเวลา 3 วินาทีเราคิดว่าสามเธรด "T1", "T2" และ "T3" ทั้งหมดกำลังทำงานอยู่ ใช้ "T1" เป็นตัวอย่าง จะรอให้เธรดอื่นปลุกพวกเขาผ่าน nofity () หรือ nofityall ()
(03) เธรดหลักจะนอนเป็นเวลา 3 วินาทีแล้ววิ่ง ดำเนินการ obj.notifyall () เพื่อปลุกเธรดรอบน OBJ นั่นคือปลุกสามเธรด "T1", "T2" และ "T3" ทันทีหลังจากเรียกใช้งาน Synchronized (OBJ) ของเธรดหลักแล้วเธรดหลักจะปล่อย "OBJ Lock" ด้วยวิธีนี้ "T1", "T2" และ "T3" สามารถรับ "OBJ Lock" และวิ่งต่อไปได้!
5. ทำไมการแจ้งเตือน () รอ () และฟังก์ชั่นอื่น ๆ ที่กำหนดไว้ในวัตถุไม่ใช่เธรด
ฟังก์ชั่นเช่น WAIT (), แจ้ง () ในวัตถุเช่นซิงโครไนซ์จะทำงานกับ "ล็อคการซิงโครไนซ์วัตถุ"
รอ () จะทำให้ "เธรดปัจจุบัน" รอ ไม่สามารถวิ่งได้!
ตกลงหลังจากการเรียกเธรดรอ () มันจะปล่อย "การล็อคแบบซิงโครนัส" ที่ถือโดยล็อคของมัน ตอนนี้โปรดคิดเกี่ยวกับคำถาม: อะไรคือการแจ้งเตือน () ตามการปลุกเธรดที่รอคอย? หรือความสัมพันธ์ระหว่างการรอ () และแจ้ง () คืออะไร? คำตอบคือ: ขึ้นอยู่กับ "ล็อคการซิงโครไนซ์วัตถุ"
เธรดที่รับผิดชอบในการตื่นเธรดรอ (เราเรียกมันว่า "Wake Up Thread") มันจะได้รับ "การซิงค์ล็อคของวัตถุ" เท่านั้น (การล็อคการซิงโครไนซ์ที่นี่จะต้องเหมือนกับการล็อคการซิงโครไนซ์ของเธรดรอ) และการโทรแจ้ง () หรือหลังวิธีการแจ้งเตือน () สามารถปลุกเธรดรอได้ แม้ว่าเธรดที่รอคอยจะถูกปลุกขึ้นมา คุณต้องรอจนกว่าเธรดปลุกจะปล่อย "ล็อคการซิงโครไนซ์ของวัตถุ" ก่อนที่คุณจะได้รับ "ล็อคการซิงโครไนซ์ของวัตถุ" และทำงานต่อไป
ในระยะสั้นแจ้ง (), รอ () ขึ้นอยู่กับ "การล็อคแบบซิงโครนัส" ซึ่งจัดขึ้นโดยล็อควัตถุและแต่ละวัตถุมีและเพียงหนึ่งเดียว! นี่คือเหตุผลที่ฟังก์ชั่นเช่น Notify (), Wait () ถูกกำหนดไว้ในคลาส Object ไม่ใช่ในคลาสเธรด