การซิงโครไนซ์เธรด Java <br /> เมื่อสองเธรดขึ้นไปจำเป็นต้องแบ่งปันทรัพยากรพวกเขาต้องการวิธีการตรวจสอบว่าทรัพยากรถูกครอบครองโดยเธรดเดียวในช่วงเวลาหนึ่ง กระบวนการบรรลุเป้าหมายนี้เรียกว่าการซิงโครไนซ์ อย่างที่คุณเห็น Java ให้การสนับสนุนระดับภาษาที่ไม่เหมือนใครสำหรับเรื่องนี้
กุญแจสำคัญในการซิงโครไนซ์คือแนวคิดของท่อ (เรียกอีกอย่างว่า Semaphore) mutex เป็นวัตถุที่ล็อค mutex หรือ mutex ในเวลาที่กำหนดมีเพียงเธรดเดียวเท่านั้นที่สามารถรับกระบวนการจัดการได้ เมื่อต้องล็อคเธรดจะต้องป้อนไปป์ไลน์ เธรดอื่น ๆ ทั้งหมดที่พยายามป้อนท่อที่ล็อคจะต้องถูกระงับจนกว่าเธรดแรกจะออกจากท่อ หัวข้ออื่น ๆ เหล่านี้เรียกว่ากระบวนการจัดการรอ เธรดที่มีกระบวนการจัดการสามารถป้อนกระบวนการจัดการเดียวกันอีกครั้งหากต้องการ
หากคุณใช้การซิงโครไนซ์ในภาษาอื่น ๆ เช่น C หรือ C ++ คุณจะรู้ว่ามันค่อนข้างแปลกที่จะใช้ นี่เป็นเพราะหลายภาษาไม่สนับสนุนการซิงโครไนซ์ด้วยตนเอง สำหรับเธรดแบบซิงโครนัสโปรแกรมจะต้องใช้ภาษาต้นทางของระบบปฏิบัติการ โชคดีที่ Java ใช้การซิงโครไนซ์ผ่านองค์ประกอบภาษาและความซับซ้อนที่เกี่ยวข้องกับการซิงโครไนซ์ส่วนใหญ่จะถูกกำจัด
คุณสามารถซิงโครไนซ์รหัสได้สองวิธี ทั้งสองรวมถึงการใช้คำหลักที่ซิงโครไนซ์และต่อไปนี้เป็นสองวิธี
ใช้วิธีการซิงโครไนซ์
การซิงโครไนซ์ใน Java นั้นง่ายเพราะวัตถุทั้งหมดมีขั้นตอนโดยนัยที่สอดคล้องกัน การป้อนกระบวนการจัดการของวัตถุบางอย่างคือการเรียกใช้วิธีการที่แก้ไขโดยคำหลักที่ซิงโครไนซ์ เมื่อเธรดอยู่ในวิธีการซิงโครไนซ์เธรดอื่น ๆ ทั้งหมดในอินสแตนซ์เดียวกันกับที่พยายามเรียกวิธีการ (หรือวิธีการซิงโครไนซ์อื่น ๆ ) ต้องรอ เพื่อที่จะออกจากกระบวนการจัดการและให้การควบคุมวัตถุไปยังเธรดที่รอคอยอื่น ๆ เธรดที่มีกระบวนการจัดการจะต้องส่งคืนจากวิธีการซิงโครไนซ์เท่านั้น
เพื่อให้เข้าใจถึงความจำเป็นในการซิงโครไนซ์ให้เริ่มต้นด้วยตัวอย่างง่ายๆที่ควรใช้การซิงโครไนซ์ แต่ไม่มีประโยชน์ โปรแกรมต่อไปนี้มีสามคลาสง่าย ๆ อันดับแรกคือ Callme ซึ่งมีวิธีการโทรง่าย ๆ () เมธอดการโทร () มีพารามิเตอร์สตริงชื่อผงชูรส วิธีนี้พยายามพิมพ์สตริงผงชูรสในวงเล็บเหลี่ยม สิ่งที่น่าสนใจคือหลังจากการโทร () พิมพ์วงเล็บซ้ายและสตริงผงชูรสเธรด. sleep (1,000) เรียกว่าซึ่งหยุดเธรดปัจจุบันเป็นเวลา 1 วินาที
ตัวสร้างของผู้เรียกคลาสถัดไปหมายถึงอินสแตนซ์ของ callme และสตริงซึ่งมีอยู่ในเป้าหมายและผงชูรสตามลำดับ ตัวสร้างยังสร้างเธรดใหม่ที่เรียกวิธีการเรียกใช้ () ของวัตถุ เธรดเริ่มต้นทันที วิธีการเรียกใช้ () ของคลาสผู้โทรเรียกใช้เมธอดการโทร () ของเป้าหมายอินสแตนซ์ CallMe ผ่านสตริงผงชูรส ในที่สุดคลาสซิงค์เริ่มต้นด้วยการสร้างอินสแตนซ์ง่ายๆของ CallMe และสามอินสแตนซ์ของผู้โทรที่มีสตริงข้อความที่แตกต่างกัน
อินสแตนซ์เดียวกันของ CallMe ถูกส่งผ่านไปยังอินสแตนซ์ของผู้โทรแต่ละคน
// โปรแกรมนี้ไม่ได้ซิงโครไนซ์ callme {โมฆะ call (string msg) {system.out.print ("[" + msg); ออก. println ("interrupted");} system.out.println ("]"); TARG; Cal LME Target = New CallMe (); รอให้จบการทดลอง {ob1.t.join (); }}
ผลลัพธ์ของโปรแกรมนี้มีดังนี้:
สวัสดี [ซิงโครไนซ์ [โลก]]]
ในตัวอย่างนี้โดยการโทร sleep () วิธีการโทร () อนุญาตให้ดำเนินการดำเนินการกับเธรดอื่น ผลลัพธ์นี้เป็นเอาท์พุทผสมของสามข้อความ ในโปรแกรมนี้ไม่มีวิธีใดที่ป้องกันไม่ให้มีสามเธรดจากการเรียกใช้วิธีเดียวกันของวัตถุเดียวกันในเวลาเดียวกัน นี่คือการแข่งขันเพราะสามเธรดแข่งขันเพื่อทำวิธีการ ตัวอย่างใช้การนอนหลับ () เพื่อให้เอฟเฟกต์ซ้ำและชัดเจน ในกรณีส่วนใหญ่การแข่งขันมีความซับซ้อนและคาดเดาไม่ได้มากขึ้นเพราะคุณไม่สามารถแน่ใจได้ว่าการแปลงบริบทจะเกิดขึ้นเมื่อใด สิ่งนี้ทำให้โปรแกรมทำงานตามปกติและบางครั้งก็ผิดพลาด
เพื่อให้บรรลุวัตถุประสงค์ที่ต้องการในตัวอย่างข้างต้นคุณต้องมีสิทธิ์ใช้การโทร () นั่นคือในบางจุดมันจะต้อง จำกัด เพียงเธรดเดียวที่สามารถควบคุมได้ ในการทำเช่นนี้คุณเพียงแค่ต้องเตรียมการกำหนด () คำจำกัดความที่ซิงโครไนซ์ดังต่อไปนี้:
คลาส callme {call void call (string msg) {...
สิ่งนี้จะป้องกันไม่ให้เธรดอื่นเข้าสู่การโทร () เมื่อหนึ่งเธรดใช้การโทร () หลังจากซิงโครไนซ์ถูกเพิ่มลงในการโทร () เอาต์พุตโปรแกรมดังนี้:
[สวัสดี] [ซิงโครไนซ์] [โลก]
เมื่อใดก็ตามที่ในสถานการณ์มัลติเธรดหากคุณมีวิธีเดียวหรือหลายวิธีในการจัดการสถานะภายในของวัตถุคุณต้องใช้คำหลักที่ซิงโครไนซ์เพื่อป้องกันการแข่งขันของรัฐ โปรดจำไว้ว่าเมื่อเธรดเข้าสู่วิธีการซิงโครไนซ์ของอินสแตนซ์จะไม่มีเธรดอื่นใดสามารถป้อนวิธีการซิงโครไนซ์ของอินสแตนซ์เดียวกัน อย่างไรก็ตามวิธีการ async อื่น ๆ ของอินสแตนซ์นี้ยังสามารถเรียกได้
คำสั่งแบบซิงโครนัส
แม้ว่าการสร้างวิธีการซิงโครไนซ์ภายในคลาสที่สร้างขึ้นนั้นเป็นวิธีที่ง่ายและมีประสิทธิภาพในการซิงโครไนซ์ แต่ก็ไม่มีประสิทธิภาพตลอดเวลา โปรดคิดเกี่ยวกับเหตุผลที่อยู่เบื้องหลัง สมมติว่าคุณต้องการได้รับการเข้าถึงวัตถุคลาสที่ซิงโครไนซ์ที่ไม่ได้ออกแบบมาสำหรับการเข้าถึงแบบมัลติเธรดนั่นคือคลาสไม่ได้ใช้วิธีการซิงโครไนซ์ ยิ่งกว่านั้นชั้นเรียนไม่ได้สร้างขึ้นโดยคุณ แต่โดยบุคคลที่สามและคุณไม่สามารถรับซอร์สโค้ดได้ ด้วยวิธีนี้คุณไม่สามารถเตรียมตัวดัดแปลงแบบซิงโครไนซ์ไว้ล่วงหน้าบนวิธีที่เกี่ยวข้อง วัตถุของคลาสนี้สามารถซิงโครไนซ์ได้อย่างไร? โชคดีที่การแก้ปัญหานั้นง่าย: คุณเพียงแค่ต้องทำการโทรไปยังวิธีที่กำหนดโดยคลาสนี้ลงในบล็อกที่ซิงโครไนซ์
นี่คือรูปแบบปกติของคำสั่งซิงโครไนซ์:
ซิงโครไนซ์ (วัตถุ) {// คำสั่งที่จะซิงโครไนซ์}
โดยที่วัตถุคือการอ้างอิงถึงวัตถุที่ซิงโครไนซ์ หากสิ่งที่คุณต้องการซิงโครไนซ์เป็นเพียงคำสั่งก็ไม่จำเป็นต้องมีการจัดฟันแบบหยิก บล็อกการซิงโครไนซ์ทำให้มั่นใจได้ว่าการเรียกไปยังวิธีการสมาชิกวัตถุจะเกิดขึ้นหลังจากเธรดปัจจุบันจะเข้าสู่ไปป์ไลน์ของวัตถุได้สำเร็จ
ต่อไปนี้เป็นเวอร์ชันที่แก้ไขของโปรแกรมก่อนหน้าและบล็อกการซิงโครไนซ์ถูกใช้ในวิธีการเรียกใช้ ():
// โปรแกรมนี้ใช้ block.class callme {void call (string msg) {system.out.print ("[" + msg); out.println ("interrupted");} system.out.println ("]"); MSG = S; ; "); ผู้โทร OB3 = ผู้โทรใหม่ (เป้าหมาย," โลก "); // รอให้เธรดจบลอง {ob1.t.join (); ob2.t.join (); ob3.t.join ();} Catch (InterruptedException E) {System.out.println ("ถูกขัดจังหวะ");}}}
ที่นี่วิธีการโทร () ไม่ได้รับการแก้ไขโดยซิงโครไนซ์ การซิงโครไนซ์ถูกประกาศในวิธีการเรียกใช้ () ของคลาสผู้โทร สิ่งนี้ให้ผลลัพธ์ที่ถูกต้องเหมือนกันในตัวอย่างด้านบนเนื่องจากแต่ละเธรดรอการสิ้นสุดของเธรดก่อนหน้าก่อนที่จะทำงาน
Java Inter-Thread Communication <br /> multithreading แทนที่โปรแกรมลูปเหตุการณ์โดยการแบ่งงานเป็นหน่วยที่ไม่ต่อเนื่องและตรรกะ เธรดมีข้อได้เปรียบที่สอง: มันอยู่ห่างจากการสำรวจ การสำรวจมักจะทำได้โดยลูปที่ทำซ้ำเงื่อนไขการตรวจสอบ เมื่อมีการกำหนดเงื่อนไขแล้วจะต้องดำเนินการที่เหมาะสม สิ่งนี้เสียเวลา CPU ตัวอย่างเช่นพิจารณาปัญหาลำดับคลาสสิกเมื่อเธรดหนึ่งกำลังสร้างข้อมูลในขณะที่โปรแกรมอื่นใช้มัน เพื่อให้ปัญหาน่าสนใจยิ่งขึ้นสมมติว่าตัวสร้างข้อมูลต้องรอให้ผู้บริโภคทำงานให้เสร็จก่อนที่จะสามารถสร้างข้อมูลใหม่ได้ ในระบบการเลือกตั้งผู้บริโภคเสียวงจร CPU จำนวนมากในขณะที่รอผู้ผลิตสร้างข้อมูล เมื่อผู้ผลิตทำงานให้เสร็จสมบูรณ์มันจะเริ่มต้นการสำรวจโดยเสียเวลา CPU มากขึ้นเพื่อรอให้งานของผู้บริโภคสิ้นสุดลงเรื่อย ๆ เห็นได้ชัดว่าสถานการณ์นี้ไม่ได้รับความนิยม
เพื่อหลีกเลี่ยงการเลือกตั้ง Java รวมถึงกลไกการสื่อสารระหว่างกระบวนการที่ดำเนินการผ่านวิธี WAIT (), Notify () และ NOTIFYALL () วิธีการเหล่านี้ถูกนำไปใช้ในวัตถุที่มีวิธีการสุดท้ายดังนั้นคลาสทั้งหมดจึงมี วิธีการทั้งสามนี้สามารถเรียกใช้ในวิธีการซิงโครไนซ์เท่านั้น แม้ว่าวิธีการเหล่านี้จะมีความก้าวหน้าสูงในแนวคิดจากมุมมองของวิสัยทัศน์วิทยาศาสตร์คอมพิวเตอร์ แต่ก็ใช้งานง่ายมากในทางปฏิบัติ:
รอ () บอกเธรดที่เรียกว่าให้ขึ้นไปท่อและเข้านอนจนกว่าเธรดอื่นจะป้อนท่อเดียวกันและโทรแจ้ง ()
แจ้งเตือน () เรียกคืนเธรดแรกในวัตถุเดียวกันที่จะโทรรอ ()
NotifyAll () กู้คืนเธรดทั้งหมดในวัตถุเดียวกันกับที่การโทรรอ () เธรดที่มีลำดับความสำคัญสูงสุดทำงานก่อน
วิธีการเหล่านี้ถูกประกาศในวัตถุดังนี้:
Void Wait ครั้งสุดท้าย () พ่น InterruptedException สุดท้ายเป็นโมฆะ Notify () Void Notifyall ขั้นสุดท้าย ()
อีกรูปแบบหนึ่งของการรอคอย () ช่วยให้คุณสามารถกำหนดเวลารอได้
ตัวอย่างข้อผิดพลาดของโปรแกรมต่อไปนี้ใช้ปัญหาผู้ผลิต/ผู้บริโภคอย่างง่าย มันประกอบด้วยสี่คลาส: Q ซึ่งสามารถรับลำดับที่ซิงโครไนซ์;
// การใช้งานที่ไม่ถูกต้องของผู้ผลิตและผู้บริโภค class q {int n; สิ่งนี้. n = n; ). start ();} public void run () {int i = 0; = Q; ) {q q = ใหม่ q ();
แม้ว่าวิธีการ put () และ get () ในคลาส Q จะถูกซิงโครไนซ์ แต่ไม่มีอะไรป้องกันไม่ให้ผู้ผลิตเกินผู้บริโภคและไม่มีสิ่งใดที่ป้องกันไม่ให้ผู้บริโภคบริโภคลำดับเดียวกันสองครั้ง วิธีนี้คุณจะได้รับเอาต์พุตข้อผิดพลาดต่อไปนี้ (เอาต์พุตจะเปลี่ยนไปตามความเร็วโปรเซสเซอร์และงานที่โหลด):
Put: 1GOT: 1GOT: 1GOT: 1GOT: 1GOT: 1PUT: 2PUT: 3PUT: 4PUT: 5PUT: 6PUT: 7GOT: 7
หลังจากผู้ผลิตสร้าง 1 ผู้บริโภคจะได้รับ 15 ครั้งเดียวกัน ผู้ผลิตยังคงสร้าง 2 ถึง 7 และผู้บริโภคไม่มีโอกาสได้รับพวกเขา
หากต้องการเขียนโปรแกรมนี้อย่างถูกต้องใน Java ให้ใช้ Wait () และแจ้งเตือน () เพื่อทำเครื่องหมายทั้งสองทิศทางดังที่แสดงด้านล่าง:
// การใช้งานที่ถูกต้องของผู้ผลิตและผู้บริโภค class q {int n; . println ("InterruptedException"); Waite (); )}} ผู้ผลิตคลาสใช้งาน {q q; ในขณะที่ (จริง) {q.put (i ++); โมฆะสาธารณะเรียกใช้ () {ในขณะที่ (จริง) {q.get ();}}} คลาส pcfixed {โมฆะสาธารณะคง (q);
internal get (), รอ () เรียกว่า สิ่งนี้แขวนการดำเนินการจนกว่าผู้ผลิตจะแจ้งข้อมูลว่าพร้อม ในเวลานี้ GET () ภายในจะกลับมาทำงานต่อ หลังจากได้รับข้อมูลแล้วให้รับ () การโทรแจ้ง () สิ่งนี้บอกผู้ผลิตว่าคุณสามารถป้อนข้อมูลเพิ่มเติมลงในลำดับ ใน put (), รอ () ระงับการดำเนินการจนกว่าผู้บริโภคจะนำรายการออกไปตามลำดับ เมื่อดำเนินการดำเนินการต่อไปรายการข้อมูลถัดไปจะถูกเรียกเข้าลำดับและแจ้งเตือน () ซึ่งจะแจ้งให้ผู้บริโภคทราบว่าควรลบข้อมูลออก
นี่คือผลลัพธ์ของโปรแกรมซึ่งแสดงพฤติกรรมการซิงโครไนซ์อย่างชัดเจน:
Put: 1GOT: 1PUT: 2GOT: 2PUT: 3GOT: 3PUT: 4GOT: 4PUT: 5GOT: 5