ซีรี่ส์นี้ขึ้นอยู่กับเส้นทางของการกลั่นตัวเลขเป็นทองคำและเพื่อเรียนรู้ที่ดีขึ้นมีการบันทึกชุด บทความนี้ส่วนใหญ่แนะนำ 1. อะไรคือเธรด 2. การดำเนินการพื้นฐานของเธรด 3. เธรด Daemon 4. ลำดับความสำคัญ 5. การดำเนินการซิงโครไนซ์เธรดพื้นฐานขั้นพื้นฐาน
1. เธรดคืออะไร
เธรดเป็นหน่วยดำเนินการภายในกระบวนการ
มีหลายเธรดในกระบวนการ
เธรดเป็นหน่วยดำเนินการภายในกระบวนการ
เหตุผลในการใช้เธรดคือการสลับกระบวนการเป็นการดำเนินการที่มีน้ำหนักมากและใช้ทรัพยากร หากคุณใช้หลายกระบวนการจำนวนการเกิดขึ้นพร้อมกันจะไม่สูงมาก เธรดเป็นหน่วยการจัดตารางเวลาที่เล็กกว่าและมีน้ำหนักเบากว่าดังนั้นเธรดจะถูกใช้กันอย่างแพร่หลายในการออกแบบพร้อมกัน
ใน Java แนวคิดของเธรดนั้นคล้ายกับแนวคิดของเธรดระดับระบบปฏิบัติการ ในความเป็นจริง JVM จะแมปเธรดใน Java กับพื้นที่เธรดของระบบปฏิบัติการ
2. การดำเนินการพื้นฐานของเธรด
2.1 แผนภาพสถานะเธรด
ภาพด้านบนแสดงการทำงานพื้นฐานของเธรดใน Java
เมื่อเธรดใหม่เธรดจริงไม่ทำงาน มันเพิ่งสร้างเอนทิตีและเธรดเริ่มต้นขึ้นจริงเมื่อคุณเรียกวิธีการเริ่มต้นของอินสแตนซ์นี้ หลังจากเริ่มต้นมันมาถึงสถานะที่รันได้ Runnable หมายความว่าทรัพยากรของเธรดและอื่น ๆ ได้รับการเตรียมและสามารถดำเนินการได้ แต่ไม่ได้หมายความว่ามันอยู่ในสถานะการดำเนินการ เนื่องจากการหมุนของชิ้นเวลาเธรดอาจไม่ถูกดำเนินการในเวลานี้ สำหรับเราแล้วเธรดสามารถพิจารณาได้ว่าถูกดำเนินการ แต่ไม่ว่าจะดำเนินการจริงหรือไม่นั้นขึ้นอยู่กับการกำหนดเวลาของ CPU ทางกายภาพ เมื่องานเธรดถูกดำเนินการเธรดจะถึงสถานะที่สิ้นสุด
บางครั้งในระหว่างการดำเนินการของเธรดมันเป็นสิ่งที่หลีกเลี่ยงไม่ได้ที่ล็อคหรือจอภาพวัตถุบางตัวจะถูกนำไปใช้ เมื่อไม่สามารถเรียกคืนได้เธรดจะถูกบล็อกระงับและถึงสถานะที่ถูกบล็อก หากเธรดนี้เรียกวิธีการรอมันอยู่ในสถานะรอ เธรดที่เข้าสู่สถานะการรอคอยจะรอให้เธรดอื่นแจ้งให้ทราบ หลังจากมีการแจ้งเตือนสถานะการรอคอยจะเปลี่ยนจากสถานะรอเป็นสถานะที่เรียกใช้เพื่อดำเนินการต่อ แน่นอนว่ามีสองประเภทของการรอคอยหนึ่งคือการรออย่างไม่มีกำหนดจนกว่าจะได้รับแจ้ง มันรอระยะเวลา จำกัด ตัวอย่างเช่นหากคุณรอ 10 วินาทีหรือไม่ได้รับแจ้งจะเปลี่ยนไปเป็นสถานะที่เรียกใช้โดยอัตโนมัติ
2.2 สร้างเธรดใหม่
เธรดเธรด = ใหม่เธรด ();
thread.start ();
สิ่งนี้เปิดเธรด
สิ่งหนึ่งที่ควรทราบคือ
เธรดเธรด = ใหม่เธรด ();
thread.run ();
คุณไม่สามารถเปิดเธรดใหม่โดยเรียกใช้วิธีการเรียกใช้โดยตรง
วิธีการเริ่มต้นเรียกใช้วิธีการเรียกใช้ในเธรดระบบปฏิบัติการใหม่ กล่าวอีกนัยหนึ่งถ้าคุณเรียกวิธีการเรียกใช้โดยตรงแทนที่จะเป็นวิธีการเริ่มต้นมันจะไม่เริ่มเธรดใหม่ แต่จะดำเนินการในการเรียกใช้เธรดปัจจุบัน
เธรดเธรด = เธรดใหม่ ("t1") {@Override โมฆะสาธารณะเรียกใช้ () {// วิธีการที่สร้างอัตโนมัติวิธีการ stub system.out.println (thread.currentthread (). getName ()); }}; thread.start (); หากเรียกว่าการเริ่มต้นเอาต์พุตจะเป็นเธรด t1Thread = เธรดใหม่ ("t1") {@Override โมฆะสาธารณะเรียกใช้ () {// toDo วิธีการที่สร้างขึ้นอัตโนมัติระบบ Stub System.out.println (thread.currentthread () getName ()); }}; thread.run (); ถ้ามันทำงานหลักคือเอาต์พุต (การเปิดใช้งานโดยตรงเป็นเพียงการเรียกฟังก์ชั่นทั่วไปและไม่ได้รับบทบาทของมัลติเธรด)
มีสองวิธีในการใช้วิธีการเรียกใช้
วิธีแรกคือการแทนที่วิธีการเรียกใช้โดยตรง ดังที่แสดงในรหัสตอนนี้วิธีที่สะดวกที่สุดสามารถทำได้โดยใช้คลาสที่ไม่ระบุชื่อ
เธรดเธรด = เธรดใหม่ ("t1") {@Override โมฆะสาธารณะเรียกใช้ () {// วิธีการที่สร้างอัตโนมัติวิธีการ stub system.out.println (thread.currentthread (). getName ()); - วิธีที่สอง
เธรด t1 = เธรดใหม่ (ใหม่ createthread3 ());
CreateThread3 () ใช้อินเทอร์เฟซที่รันได้
ในวิดีโอของ Zhang Xiaoxiang แนะนำวิธีที่สองโดยบอกว่าเป็นวัตถุที่มุ่งเน้นมากขึ้น
2.3 เลิกเธรด
ไม่แนะนำให้ใช้ thread.stop () มันปล่อยมอนิเตอร์ทั้งหมด
มันได้รับการระบุไว้อย่างชัดเจนในซอร์สโค้ดว่าวิธีการหยุดไม่เลิกและเหตุผลก็อธิบายไว้ใน Javadoc
เหตุผลก็คือวิธีการหยุดนั้น "รุนแรง" เกินไป ไม่ว่าเธรดจะดำเนินการที่ไหนมันจะหยุดวางเธรดทันที
เมื่อเธรดเขียนได้รับการล็อคเริ่มเขียนข้อมูล หลังจากเขียน ID = 1 มันจะหยุดและล็อคจะถูกปล่อยออกมาเมื่อเตรียมที่จะตั้งชื่อ = 1. เธรดอ่านจะได้รับการล็อคสำหรับการดำเนินการอ่าน การอ่าน ID คือ 1 และชื่อยังคงเป็น 0 ซึ่งทำให้ข้อมูลไม่สอดคล้องกัน
สิ่งที่สำคัญที่สุดคือข้อผิดพลาดประเภทนี้จะไม่ทำให้เกิดข้อยกเว้นและจะตรวจจับได้ยาก
2.4 เธรดขัดจังหวะ
มี 3 วิธีในการขัดจังหวะเธรด
โมฆะสาธารณะ Thread.interrupt () // อินเตอร์รัปต์เธรด
Public Boolean Thread.isinterrupted () // พิจารณาว่ามันถูกขัดจังหวะว่า
เธรดบูลีนแบบคงที่สาธารณะ interrupted () // พิจารณาว่ามันถูกขัดจังหวะและล้างสถานะการขัดจังหวะปัจจุบัน
การหยุดชะงักของเธรดคืออะไร?
หากคุณไม่เข้าใจกลไกการขัดจังหวะของ Java คำอธิบายดังกล่าวมีแนวโน้มที่จะทำให้เกิดความเข้าใจผิดและคุณเชื่อว่าการเรียกวิธีการขัดจังหวะของเธรดจะขัดจังหวะเธรดอย่างแน่นอน
ในความเป็นจริงการหยุดชะงักของ Java เป็นกลไกการทำงานร่วมกัน กล่าวอีกนัยหนึ่งการเรียกใช้วิธีการขัดจังหวะของวัตถุเธรดไม่จำเป็นต้องขัดจังหวะเธรดที่รันมันเพียงแค่ต้องใช้เธรดเพื่อขัดจังหวะตัวเองในเวลาที่เหมาะสม แต่ละเธรดมีสถานะขัดจังหวะบูลีน (ไม่จำเป็นต้องเป็นคุณสมบัติของวัตถุในความเป็นจริงสถานะนี้ไม่ใช่ฟิลด์เธรด) และวิธีการขัดจังหวะเพียงแค่กำหนดสถานะให้เป็นจริง สำหรับเธรดที่ไม่ปิดกั้นสถานะการขัดจังหวะจะเปลี่ยนไปนั่นคือ Thread.isinterrupted () จะส่งคืนจริงและจะไม่หยุดโปรแกรม
โมฆะสาธารณะเรียกใช้ () {// เธรด t1 ในขณะที่ (จริง) {thread.yield (); }} t1.interrupt (); สิ่งนี้จะไม่มีผลกระทบใด ๆ ในการขัดจังหวะเธรด T1 แต่บิตสถานะอินเตอร์รัปต์จะเปลี่ยนไป
หากคุณต้องการยุติหัวข้อนี้อย่างสง่างามคุณควรทำสิ่งนี้
โมฆะสาธารณะเรียกใช้ () {ในขณะที่ (จริง) {if (thread.currentthread (). isInterrupted ()) {system.out.println ("interruted!"); หยุดพัก; } thread.yield (); -การใช้การขัดจังหวะเป็นการรับประกันความสอดคล้องของข้อมูล
สำหรับเธรดในสถานะการปิดกั้นที่ยกเลิกได้เช่นเธรดที่รอฟังก์ชั่นเหล่านี้, thread.sleep (), object.wait () และ thread.join (), เธรดนี้จะโยน interruptedexception หลังจากได้รับสัญญาณขัดจังหวะและจะตั้งสถานะการขัดจังหวะกลับเป็นเท็จ
สำหรับเธรดในสถานะปลดล็อคคุณสามารถเขียนรหัสเช่นนี้:
โมฆะสาธารณะเรียกใช้ () {ในขณะที่ (จริง) {if (thread.currentthread (). isInterrupted ()) {system.out.println ("interruted!"); หยุดพัก; } ลอง {thread.sleep (2000); } catch (interruptedException e) {system.out.println ("ไม่หยุดยั้งเมื่อนอนหลับ"); // ตั้งค่าสถานะการขัดจังหวะ บิตอินเตอร์รัปต์จะถูกล้างหลังจากโยนเธรดข้อยกเว้น currentthread () interrupt (); } thread.yield (); -2.5 ด้ายแขวน
ระงับและกลับมาทำงาน
Suspend () จะไม่ปล่อยล็อค
หากการล็อคเกิดขึ้นก่อนที่จะดำเนินการต่อ () ทั้งสองวิธีของการหยุดชะงักที่เกิดขึ้นเป็นวิธีการเลิกใช้และไม่แนะนำ
เหตุผลก็คือ Suspend ไม่ปล่อยล็อคดังนั้นจึงไม่มีเธรดที่สามารถเข้าถึงทรัพยากรพื้นที่วิกฤตที่ถูกล็อคโดยจนกว่าจะกลับมาทำงานต่อโดยเธรดอื่น เนื่องจากลำดับของเธรดที่ทำงานไม่สามารถควบคุมได้หากวิธีการดำเนินการต่อของเธรดอื่น ๆ ถูกเรียกใช้ก่อนการระงับในภายหลังจะครอบครองล็อคอยู่เสมอทำให้เกิดการหยุดชะงัก
ใช้รหัสต่อไปนี้เพื่อจำลองสถานการณ์นี้
การทดสอบแพ็คเกจ; การทดสอบระดับสาธารณะ {วัตถุคงที่ u = วัตถุใหม่ (); Static TestSuspendThread T1 = New TestSuspendThread ("T1"); Static TestSuspendThread T2 = TestSuspendThread ใหม่ ("T2"); Public Static Class TestSuspendThread ขยายเธรด {Public TestSusPendThread (ชื่อสตริง) {setName (ชื่อ); } @Override โมฆะสาธารณะเรียกใช้ () {ซิงโครไนซ์ (u) {system.out.println ("in" + getName ()); thread.currentthread (). suspend (); }} โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่น InterruptedException {t1.start (); Thread.sleep (100); t2.start (); t1.resume (); t2.resume (); t1.join (); t2.join (); - ปล่อยให้ T1 และ T2 แข่งขันเพื่อล็อคในเวลาเดียวกันเธรดที่มีการแข่งขันจะถูกระงับแล้วกลับมาทำงานต่อ การพูดอย่างมีเหตุผลเธรดควรได้รับการปล่อยตัวโดยเรซูเม่หลังจากแข่งขันกับมันแล้วเธรดอื่นแข่งขันสำหรับการล็อคและประวัติย่อ
ผลลัพธ์ผลลัพธ์คือ:
ใน T1
ใน t2
ซึ่งหมายความว่าเธรดทั้งสองแข่งขันกันเพื่อล็อค แต่แสงสีแดงบนคอนโซลยังคงเปิดอยู่ซึ่งหมายความว่าจะต้องมีเธรดที่ไม่ได้ดำเนินการใน T1 และ T2 มาทิ้งมันและดู
พบว่า T2 ถูกระงับ สิ่งนี้สร้างการหยุดชะงัก
2.6 เข้าร่วมและ Yeild
Yeild เป็นวิธีการคงที่ วิธีนี้คือการปล่อยเวลา CPU ที่เป็นเจ้าของแล้วแข่งขันกับเธรดอื่น ๆ (โปรดทราบว่าเธรดของ Yeild อาจยังคงแข่งขันกับ CPU ให้ความสนใจกับความแตกต่างจากการนอนหลับ) นอกจากนี้ยังมีการอธิบายใน Javadoc ว่า Yeild เป็นวิธีที่ไม่ได้ใช้โดยทั่วไปและใช้โดยทั่วไปในการดีบักและทดสอบ
วิธีการเข้าร่วมหมายถึงการรอให้เธรดอื่นสิ้นสุดเช่นเดียวกับรหัสในส่วน Suspend คุณต้องการให้เธรดหลักรอ T1 และ T2 สิ้นสุดก่อนที่จะสิ้นสุด หากยังไม่จบเธรดหลักจะถูกบล็อกที่นั่น
การทดสอบแพ็คเกจ; การทดสอบระดับสาธารณะ {สาธารณะคงที่คงที่ int i = 0; คลาสสแตติกสาธารณะ AddThread ขยายเธรด {@Override โมฆะสาธารณะเรียกใช้ () {สำหรับ (i = 0; i <10,00000000; i ++); }} โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่น InterruptedException {addThread ที่ = new addThread (); at.start (); at.oin (); System.out.println (i); - หาก at.join ของรหัสข้างต้นถูกลบออกเธรดหลักจะทำงานโดยตรงและค่าของฉันจะเล็กมาก หากมีการเข้าร่วมค่าของสิ่งที่พิมพ์ฉันต้องเป็น 10,000,000
ดังนั้นการเข้าร่วมจะถูกนำไปใช้อย่างไร?
ธรรมชาติของการเข้าร่วม
ในขณะที่ (isalive ())
-
รอ (0);
-
วิธีการเข้าร่วม () ยังสามารถผ่านเวลาซึ่งหมายถึงการรอระยะเวลาที่ จำกัด และตื่นขึ้นมาโดยอัตโนมัติหลังจากเวลานี้
มีคำถามเช่นนี้: ใครจะแจ้งเธรด? ไม่มีสถานที่ที่จะโทรแจ้งในคลาสเธรด?
ใน Javadoc พบคำอธิบายที่เกี่ยวข้อง เมื่อเธรดเสร็จสมบูรณ์และยกเลิกวิธีการแจ้งเตือนจะถูกเรียกให้ปลุกเธรดทั้งหมดที่รออยู่ในอินสแตนซ์เธรดปัจจุบัน การดำเนินการนี้ทำโดย JVM เอง
ดังนั้น Javadoc จึงให้คำแนะนำแก่เราว่าอย่าใช้การรอและแจ้ง/แจ้งเตือนเกี่ยวกับอินสแตนซ์เธรด เนื่องจาก JVM จะเรียกตัวเองว่าอาจแตกต่างจากผลลัพธ์ที่คาดหวังจากการโทรของคุณ
3. ด้ายดุล
ให้บริการอย่างเป็นระบบในพื้นหลังอย่างเงียบ ๆ เช่นเธรดคอลเลกชันขยะและเธรด JIT สามารถเข้าใจได้ว่าเป็นเธรด daemon
เมื่อกระบวนการที่ไม่ใช่วันเดียวทั้งหมดสิ้นสุดลงในแอปพลิเคชัน Java เครื่องเสมือน Java จะออกจากธรรมชาติ
ฉันได้เขียนบทความเกี่ยวกับวิธีการนำไปใช้ใน Python ตรวจสอบที่นี่
มันค่อนข้างง่ายที่จะกลายเป็น daemon ในชวา
เธรด t = new daemont ();
T.Setdaemon (จริง);
T.Start ();
สิ่งนี้จะเปิดดุลด้าย
การทดสอบแพ็คเกจ; การทดสอบระดับสาธารณะ {public class daemonthread ขยายเธรด {@Override โมฆะสาธารณะเรียกใช้ () {สำหรับ (int i = 0; i <10,00000000; i ++) {system.out.println ("hi"); }}} โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่น InterruptedException {daemonthread dt = new daemonthread (); dt.start (); - เมื่อเธรด dt ไม่ใช่เธรด daemon หลังจากทำงานแล้วเราจะเห็นเอาต์พุตคอนโซลเอาท์พุท hi
เมื่อเข้าร่วมก่อนเริ่ม
dt.setdaemon (จริง);
คอนโซลออกโดยตรงและไม่มีเอาต์พุต
4. ลำดับความสำคัญของเธรด
มี 3 ตัวแปรในคลาสเธรดที่กำหนดลำดับความสำคัญของเธรด
สาธารณะสุดท้ายคงที่ int min_priority = 1; public สุดท้ายคงที่ int norm_priority = 5; public สุดท้ายคงที่ int max_priority = 10; การทดสอบแพ็คเกจ; การทดสอบระดับสาธารณะ {คลาสคงที่ระดับสูง @Override โมฆะสาธารณะ Run () {ในขณะที่ (จริง) {ซิงโครไนซ์ (test.class) {count ++; if (count> 10,000,000) {system.out.println ("สูง"); หยุดพัก; }}}}} คลาสสแตติกสาธารณะต่ำขยายเธรด {count int คงที่ = 0; @Override โมฆะสาธารณะ Run () {ในขณะที่ (จริง) {ซิงโครไนซ์ (test.class) {count ++; if (count> 10,000,000) {system.out.println ("ต่ำ"); หยุดพัก; }}}}} โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่น InterruptedException {สูง high = ใหม่สูง (); ต่ำต่ำ = ใหม่ต่ำ (); High.setPriority (thread.max_priority); low.setPriority (thread.min_priority); low.start (); High.start (); - ให้เธรดที่มีลำดับความสำคัญสูงและเธรดที่มีลำดับความสำคัญต่ำจะแข่งขันเพื่อล็อคในเวลาเดียวกันและดูว่าอันไหนเสร็จก่อน
แน่นอนว่าไม่จำเป็นต้องเสร็จสิ้นก่อนโดยมีลำดับความสำคัญสูง หลังจากเรียกใช้หลายครั้งฉันพบว่าความน่าจะเป็นของการเพิ่มลำดับความสำคัญสูงนั้นค่อนข้างสูง แต่ก็ยังเป็นไปได้ที่จะทำให้เสร็จก่อนด้วยลำดับความสำคัญต่ำ
5. การดำเนินการซิงโครไนซ์เธรดพื้นฐาน
ซิงโครไนซ์และ object.wait () obejct.notify ()
สำหรับรายละเอียดของส่วนนี้โปรดดูบล็อกที่ฉันเขียนไว้ก่อนหน้านี้
สิ่งสำคัญที่ควรทราบคือ
มีสามวิธีในการล็อคซิงโครไนซ์:
ระบุวัตถุที่ล็อค: ล็อควัตถุที่กำหนดและรับล็อคของวัตถุที่กำหนดก่อนป้อนรหัสการซิงโครไนซ์
ทำหน้าที่โดยตรงกับวิธีการอินสแตนซ์: เทียบเท่ากับการล็อคอินสแตนซ์ปัจจุบัน ก่อนที่จะป้อนรหัสการซิงโครไนซ์คุณจะต้องได้รับการล็อคของอินสแตนซ์ปัจจุบัน
ทำหน้าที่โดยตรงกับวิธีการคงที่: เทียบเท่ากับการล็อคคลาสปัจจุบัน ก่อนที่จะป้อนรหัสแบบซิงโครนัสคุณจะต้องได้รับการล็อคของคลาสปัจจุบัน
หากใช้งานกับวิธีการอินสแตนซ์อย่าใหม่สองอินสแตนซ์ที่แตกต่างกัน
มันทำงานกับวิธีการคงที่ตราบใดที่คลาสเหมือนกันเพราะการล็อคที่เพิ่มขึ้นเป็นคลาสคลาสและสองอินสแตนซ์ที่แตกต่างกันอาจเป็นใหม่
วิธีใช้รอและแจ้ง:
ใช้อะไรก็ตามที่ล็อคโทรรอและแจ้งเตือน
ฉันจะไม่เข้าไปดูรายละเอียดในบทความนี้