1. ภาพรวมของเธรด
เธรดเป็นหน่วยดำเนินการพื้นฐานสำหรับการเรียกใช้โปรแกรม เมื่อระบบปฏิบัติการ (ไม่รวมระบบปฏิบัติการแบบเธรดเดียว เช่น DOS รุ่นแรกๆ ของ Microsoft) รันโปรแกรม กระบวนการจะถูกสร้างขึ้นในระบบ และในกระบวนการนี้ จะต้องสร้างเธรดอย่างน้อยหนึ่งเธรด (เธรดนี้เรียกว่าเธรดหลัก thread) เป็นจุดเริ่มต้นสำหรับให้โปรแกรมนี้ทำงาน ดังนั้นโปรแกรมใด ๆ ที่ทำงานอยู่ในระบบปฏิบัติการจึงมีเธรดหลักอย่างน้อยหนึ่งเธรด
กระบวนการและเธรดเป็นรูปแบบการทำงานที่สำคัญสองประการในระบบปฏิบัติการสมัยใหม่ สามารถมีได้หลายกระบวนการในระบบปฏิบัติการ รวมถึงกระบวนการของระบบ (กระบวนการที่สร้างภายในโดยระบบปฏิบัติการ) และกระบวนการผู้ใช้ (กระบวนการที่สร้างโดยโปรแกรมผู้ใช้) กระบวนการสามารถมีหนึ่งเธรดขึ้นไป ไม่มีหน่วยความจำที่ใช้ร่วมกันระหว่างกระบวนการ ซึ่งหมายความว่ากระบวนการในระบบทำงานในพื้นที่หน่วยความจำอิสระของตนเอง เธรดในกระบวนการสามารถแบ่งใช้พื้นที่หน่วยความจำที่ระบบจัดสรรให้กับกระบวนการได้
เธรดไม่เพียงแต่สามารถแบ่งใช้หน่วยความจำของกระบวนการเท่านั้น แต่ยังมีพื้นที่หน่วยความจำของตนเองอีกด้วย พื้นที่หน่วยความจำนี้เรียกอีกอย่างว่าเธรดสแต็ก มันถูกจัดสรรโดยระบบเมื่อเธรดถูกสร้างขึ้น ส่วนใหญ่จะใช้เพื่อบันทึก ข้อมูลที่ใช้ภายในเธรด เช่น เธรดสแต็ก ตัวแปรที่กำหนดในฟังก์ชันการดำเนินการ
หมายเหตุ: เธรดใด ๆ จะดำเนินการฟังก์ชันเมื่อมีการสร้างฟังก์ชันนี้เรียกว่าฟังก์ชันการดำเนินการเธรด ฟังก์ชันนี้ยังถือเป็นจุดเริ่มต้นของเธรดอีกด้วย (คล้ายกับฟังก์ชันหลักในโปรแกรม) ไม่ว่าจะใช้ภาษาหรือเทคโนโลยีใดในการสร้างเธรด ฟังก์ชันนี้จะต้องถูกดำเนินการ (การแสดงออกของฟังก์ชันนี้อาจแตกต่างกัน แต่จะมีฟังก์ชันดังกล่าวอยู่เสมอ) ตัวอย่างเช่น พารามิเตอร์ตัวที่สามของฟังก์ชัน API CreateThread ที่ใช้สร้างเธรดใน Windows คือตัวชี้ของฟังก์ชันการดำเนินการนี้
หลังจากที่ระบบปฏิบัติการแบ่งกระบวนการออกเป็นหลายเธรด เธรดเหล่านี้สามารถดำเนินการพร้อมกันภายใต้การจัดการของระบบปฏิบัติการ ซึ่งจะช่วยปรับปรุงประสิทธิภาพการทำงานของโปรแกรมได้อย่างมาก แม้ว่าการดำเนินการของเธรดจะดูเหมือนหลายเธรดที่ทำงานพร้อมกันจากมุมมองของแมโคร แต่อันที่จริงนี่เป็นเพียงการปกปิดระบบปฏิบัติการเท่านั้น เนื่องจาก CPU สามารถดำเนินการได้ครั้งละหนึ่งคำสั่งเท่านั้น จึงเป็นไปไม่ได้ที่จะดำเนินการสองงานพร้อมกันบนคอมพิวเตอร์ที่มี CPU ตัวเดียว เพื่อปรับปรุงประสิทธิภาพการทำงานของโปรแกรม ระบบปฏิบัติการจะลบเธรดเมื่อไม่ได้ใช้งาน และปล่อยให้เธรดอื่นดำเนินการ วิธีการนี้เรียกว่าการตั้งเวลาเธรด เหตุผลที่เราเห็นหลายเธรดทำงานพร้อมกันบนพื้นผิวก็เนื่องมาจากเวลาในการสลับระหว่างเธรดต่างๆ นั้นสั้นมากและภายใต้สถานการณ์ปกติการสลับเกิดขึ้นบ่อยมาก สมมติว่าเรามีเธรด A และ B ณ รันไทม์ อาจเป็นไปได้ว่าหลังจาก A ดำเนินการเป็นเวลา 1 มิลลิวินาที หลังจากสลับไปที่ B แล้ว B จะดำเนินการอีก 1 มิลลิวินาที จากนั้นสลับไปที่ A และ A ดำเนินการอีก 1 มิลลิวินาที เนื่องจาก 1 มิลลิวินาทีเป็นเรื่องยากสำหรับคนทั่วไปที่จะรับรู้ โดยภายนอกแล้วดูเหมือนว่า A และ B จะถูกดำเนินการในเวลาเดียวกัน แต่ในความเป็นจริง A และ B จะถูกดำเนินการสลับกัน
2.คุณประโยชน์ที่ด้ายนำมาให้เรา
การใช้เธรดอย่างเหมาะสมสามารถลดต้นทุนการพัฒนาและการบำรุงรักษา และยังปรับปรุงประสิทธิภาพของแอปพลิเคชันที่ซับซ้อนอีกด้วย ตัวอย่างเช่น ในแอปพลิเคชัน GUI เหตุการณ์สามารถประมวลผลได้ดีขึ้นผ่านลักษณะอะซิงโครนัสของเธรด ในโปรแกรมเซิร์ฟเวอร์แอปพลิเคชัน สามารถสร้างหลายเธรดเพื่อจัดการคำขอของไคลเอ็นต์ เธรดสามารถทำให้การใช้งานเครื่องเสมือนง่ายขึ้นได้ ตัวอย่างเช่น ตัวรวบรวมขยะของ Java Virtual Machine (JVM) มักจะทำงานในหนึ่งเธรดขึ้นไป ดังนั้น การใช้เธรดจะปรับปรุงแอปพลิเคชันของเราในห้าด้านต่อไปนี้:
1. ใช้ทรัพยากร CPU อย่างเต็มที่
คอมพิวเตอร์ส่วนใหญ่ในโลกปัจจุบันมี CPU เพียงตัวเดียว ดังนั้นจึงเป็นสิ่งสำคัญอย่างยิ่งที่จะต้องใช้ทรัพยากร CPU อย่างเต็มที่ เมื่อรันโปรแกรมแบบเธรดเดียว CPU อาจไม่ได้ใช้งานในขณะที่โปรแกรมถูกบล็อก ซึ่งจะทำให้สิ้นเปลืองทรัพยากรคอมพิวเตอร์ไปมาก การใช้มัลติเธรดในโปรแกรมสามารถเรียกใช้เธรดอื่นได้เมื่อเธรดอยู่ในโหมดสลีปหรือถูกบล็อกและ CPU ไม่ได้ใช้งาน ด้วยวิธีนี้ CPU ที่จะไม่ได้ใช้งานเป็นเรื่องยาก ดังนั้นทรัพยากร CPU จึงถูกใช้อย่างเต็มที่
2. ลดความซับซ้อนของโมเดลการเขียนโปรแกรม
หากโปรแกรมทำงานเสร็จเพียงงานเดียว ก็ให้เขียนโปรแกรมแบบเธรดเดียวแล้วเขียนโค้ดตามขั้นตอนในการปฏิบัติงาน แต่เพื่อที่จะทำงานหลายอย่างให้สำเร็จ หากคุณยังคงใช้เธรดเดียว คุณต้องกำหนดในโปรแกรมว่าแต่ละงานควรถูกดำเนินการหรือไม่และเมื่อใด เช่น แสดงชั่วโมง นาที และวินาทีของนาฬิกา หากคุณใช้เธรดเดียว คุณต้องตัดสินเวลาและมุมการหมุนของพอยน์เตอร์ทั้งสามนี้ทีละตัวในลูป หากใช้สามเธรดเพื่อจัดการการแสดงตัวชี้ทั้งสามนี้ หมายความว่าการทำงานแยกกันสำหรับแต่ละเธรด สิ่งนี้ช่วยให้นักพัฒนาเข้าใจและบำรุงรักษาโปรแกรม
3. ลดความซับซ้อนของการประมวลผลเหตุการณ์อะซิงโครนัส
เมื่อแอปพลิเคชันเซิร์ฟเวอร์ได้รับการเชื่อมต่อไคลเอนต์ที่แตกต่างกัน วิธีที่ง่ายที่สุดในการจัดการคือการสร้างเธรดสำหรับการเชื่อมต่อไคลเอนต์แต่ละรายการ เธรดการฟังยังคงรับผิดชอบในการรับฟังคำขอจากไคลเอนต์ หากแอปพลิเคชันประเภทนี้ได้รับการประมวลผลโดยเธรดเดียว เมื่อเธรดการฟังได้รับคำขอจากไคลเอ็นต์ เธรดจะเริ่มอ่านข้อมูลที่ส่งโดยไคลเอ็นต์ หลังจากอ่านข้อมูลแล้ว วิธีการอ่านจะถูกบล็อก กล่าวคือ เธรดนี้ จะไม่สามารถรับฟังคำขอของลูกค้าได้อีกต่อไป หากคุณต้องการจัดการคำขอหลายไคลเอ็นต์ในเธรดเดียว คุณต้องใช้การเชื่อมต่อซ็อกเก็ตที่ไม่บล็อกและ I/O แบบอะซิงโครนัส อย่างไรก็ตาม การใช้ I/O แบบอะซิงโครนัสนั้นยากต่อการควบคุมและเกิดข้อผิดพลาดได้มากกว่าการใช้ I/O แบบซิงโครนัส ดังนั้น เหตุการณ์แบบอะซิงโครนัส เช่น คำขอหลายรายการสามารถจัดการได้ง่ายขึ้นโดยใช้มัลติเธรดและ I/O แบบซิงโครนัส
4. ทำให้ GUI มีประสิทธิภาพมากขึ้น
เมื่อใช้เธรดเดียวในการประมวลผลเหตุการณ์ GUI ต้องใช้การวนซ้ำเพื่อสแกนหาเหตุการณ์ GUI ที่อาจเกิดขึ้นได้ตลอดเวลา ภายในลูป นอกเหนือจากการสแกนหาเหตุการณ์ GUI แล้ว จะต้องดำเนินการโค้ดโปรแกรมอื่น ๆ หากโค้ดยาวเกินไป กิจกรรม GUI จะถูก "หยุด" จนกว่าโค้ดจะถูกดำเนินการ
ในเฟรมเวิร์ก GUI สมัยใหม่ (เช่น SWING, AWT และ SWT) จะใช้เธรดการจัดส่งเหตุการณ์ (EDT) ที่แยกต่างหากเพื่อสแกนเหตุการณ์ GUI เมื่อเรากดปุ่ม ฟังก์ชันเหตุการณ์การคลิกของปุ่มจะถูกเรียกในเธรดการจัดส่งเหตุการณ์นี้ เนื่องจากงานของ EDT เป็นเพียงการสแกนเหตุการณ์ GUI เท่านั้น การตอบสนองต่อเหตุการณ์ในลักษณะนี้จึงรวดเร็วมาก
5. ประหยัดต้นทุน
โดยทั่วไปมีสามวิธีในการปรับปรุงประสิทธิภาพการทำงานของโปรแกรม:
(1) เพิ่มจำนวน CPU ในคอมพิวเตอร์
(2) เริ่มหลายกระบวนการสำหรับหนึ่งโปรแกรม
(3) ใช้หลายกระบวนการในโปรแกรม
วิธีแรกเป็นวิธีที่ง่ายที่สุด แต่ก็มีราคาแพงที่สุดเช่นกัน วิธีนี้ไม่จำเป็นต้องมีการดัดแปลงโปรแกรม ตามทฤษฎีแล้ว โปรแกรมใดๆ ก็สามารถใช้วิธีนี้เพื่อปรับปรุงประสิทธิภาพการดำเนินการได้ แม้ว่าวิธีที่สองไม่จำเป็นต้องซื้อฮาร์ดแวร์ใหม่ แต่ก็ไม่ใช่เรื่องง่ายที่จะแบ่งปันข้อมูล หากงานนี้ต้องทำให้เสร็จจำเป็นต้องมีการแบ่งปันข้อมูล วิธีนี้ไม่สะดวก และการเริ่มหลายเธรดจะใช้ระบบจำนวนมาก ทรัพยากร. วิธีที่สามชดเชยข้อบกพร่องของวิธีแรกในขณะที่สืบทอดข้อดีของมัน กล่าวอีกนัยหนึ่ง ไม่จำเป็นต้องซื้อ CPU และจะไม่กินทรัพยากรระบบจำนวนมากโดยการเริ่มเธรดมากเกินไป (ตามค่าเริ่มต้น พื้นที่หน่วยความจำที่เธรดครอบครองนั้นเล็กกว่าพื้นที่หน่วยความจำที่กระบวนการครอบครองมาก) . Multiple) และมัลติเธรดสามารถจำลองโหมดการทำงานของ CPU หลายตัวได้ ดังนั้น การใช้มัลติเธรดจึงเป็นวิธีที่ถูกที่สุดในการปรับปรุงประสิทธิภาพการทำงานของโปรแกรม
3. โมเดลเธรด Java
เนื่องจาก Java เป็นภาษาเชิงวัตถุล้วนๆ โมเดลเธรดของ Java จึงเป็นเชิงวัตถุเช่นกัน Java แค็ปซูลฟังก์ชันที่จำเป็นทั้งหมดของเธรดผ่านคลาสเธรด ในการสร้างเธรด จะต้องมีฟังก์ชันการดำเนินการเธรด ฟังก์ชันการดำเนินการเธรดนี้สอดคล้องกับวิธีการเรียกใช้ของคลาสเธรด คลาสเธรดยังมีวิธีการเริ่มต้นซึ่งรับผิดชอบในการสร้างเธรด ซึ่งเทียบเท่ากับการเรียกฟังก์ชันการสร้างเธรดของ Windows CreateThread เมื่อเรียกใช้เมธอด start หากสร้างเธรดสำเร็จแล้ว เมธอด run ของคลาส Thread จะถูกเรียกโดยอัตโนมัติ ดังนั้นคลาส Java ใด ๆ ที่สืบทอด Thread สามารถสร้างเธรดผ่านเมธอด start ของคลาส Thread หากคุณต้องการรันฟังก์ชันการดำเนินการเธรดของคุณเอง คุณต้องแทนที่วิธีการรันของคลาส Thread
นอกจากคลาส Thread ในโมเดลเธรด Java แล้ว ยังมีอินเทอร์เฟซ Runnable ที่ระบุว่าคลาส Java สามารถใช้เป็นคลาสเธรดได้หรือไม่ อินเทอร์เฟซนี้มีวิธีนามธรรมเพียงวิธีเดียวเท่านั้นที่รัน ซึ่งเป็นฟังก์ชันการดำเนินการเธรดของ Java รุ่นด้าย ดังนั้น เกณฑ์เดียวสำหรับคลาสเธรดคือคลาสใช้วิธีการรันของอินเทอร์เฟซ Runnable หรือไม่ กล่าวอีกนัยหนึ่ง คลาสที่มีฟังก์ชันการดำเนินการเธรดคือคลาสเธรด
ดังที่เห็นได้จากด้านบน มีสองวิธีในการสร้างเธรดใน Java วิธีแรกคือการสืบทอดคลาส Thread และวิธีที่สองคือการใช้อินเทอร์เฟซ Runnable และสร้างเธรดผ่าน Thread และคลาสที่ใช้ Runnable วิธีการทั้งสองนี้มีความสำคัญ กล่าวกันว่าเป็นวิธีการ นั่นคือ เธรดถูกสร้างขึ้นผ่านคลาสเธรด และเรียกใช้วิธีการเรียกใช้ แต่ความแตกต่างที่สำคัญคือเธรดถูกสร้างขึ้นโดยการสืบทอดคลาส Thread แม้ว่าจะใช้งานได้ง่ายกว่า เนื่องจาก Java ไม่รองรับการสืบทอดหลายรายการ หากคลาสเธรดนี้สืบทอดคลาส Thread ก็ไม่สามารถสืบทอดคลาสอื่นได้ ดังนั้น โมเดลเธรด Java จึงจัดเตรียมไว้ให้ วิธีการสร้างเธรดโดยใช้อินเทอร์เฟซ Runnable เพื่อให้คลาสเธรดสามารถสืบทอดคลาสที่เกี่ยวข้องกับธุรกิจแทนคลาส Thread เมื่อจำเป็น