คำนำ
ทุกคนที่คุ้นเคยกับการเขียนโปรแกรมพร้อมกันของ Java รู้ว่ากฎที่เกิดขึ้นก่อน (HB) ใน JMM (โมเดลหน่วยความจำ Java) ซึ่งกำหนดความเป็นระเบียบและการมองเห็นของการดำเนินการหลายเธรด Java ป้องกันผลกระทบของคอมไพเลอร์
มีกฎของ "เมื่อก่อน" ในภาษา Java มันเป็นความสัมพันธ์บางส่วนระหว่างการดำเนินการสองครั้งที่กำหนดไว้ในโมเดลหน่วยความจำ Java หากการดำเนินการ A เกิดขึ้นเป็นครั้งแรกในการดำเนินการ B หมายความว่าก่อนการดำเนินการ B เกิดขึ้นผลกระทบของการดำเนินการ A สามารถสังเกตได้โดยการดำเนินการ B. "อิทธิพล" รวมถึงการปรับเปลี่ยนค่าของตัวแปรที่ใช้ร่วมกันในหน่วยความจำการส่งข้อความวิธีการโทร ฯลฯ ซึ่งโดยทั่วไปไม่มีอะไรเกี่ยวข้องกับลำดับการเกิดขึ้นในเวลา หลักการนี้มีความสำคัญอย่างยิ่ง มันเป็นพื้นฐานหลักสำหรับการตัดสินว่ามีการแข่งขันในข้อมูลหรือไม่และเธรดนั้นปลอดภัยหรือไม่
ตามคำแถลงอย่างเป็นทางการ:
เมื่อตัวแปรถูกอ่านโดยหลายเธรดและเขียนโดยอย่างน้อยหนึ่งเธรดหากไม่มีความสัมพันธ์ HB ระหว่างการดำเนินการอ่านและเขียนปัญหาการแข่งขันข้อมูลจะเกิดขึ้น
เพื่อให้แน่ใจว่าเธรดที่ทำงาน B เห็นผลลัพธ์ของการดำเนินการ A (ไม่ว่า A และ B อยู่ในเธรดเดียวกัน) หลักการ HB จะต้องพบระหว่าง A และ B และถ้าไม่อาจนำไปสู่การจัดลำดับใหม่
เมื่อความสัมพันธ์ HB หายไปปัญหาการจัดเรียงใหม่อาจเกิดขึ้น
กฎสำหรับ HB คืออะไร?
ทุกคนคุ้นเคยกับสิ่งนี้มาก หนังสือและบทความส่วนใหญ่จะได้รับการแนะนำ ลองตรวจสอบสั้น ๆ ที่นี่:
ในหมู่พวกเขาฉันได้อธิบายกฎการส่งมอบซึ่งเป็นสิ่งสำคัญ วิธีการใช้กฎการจัดส่งอย่างเชี่ยวชาญเป็นกุญแจสำคัญในการบรรลุการซิงโครไนซ์
จากนั้นอธิบาย HB จากมุมมองอื่น: เมื่อการดำเนินการ HB ทำงาน B จากนั้นผลการดำเนินการของการดำเนินการ A บนตัวแปรที่ใช้ร่วมกันจะปรากฏให้เห็นถึงการดำเนินการ B.
ในเวลาเดียวกันหาก Operation B HB ดำเนินการ C ผลการดำเนินการของการดำเนินการ A บนตัวแปรที่ใช้ร่วมกันสามารถมองเห็นการดำเนินการ B.
หลักการของการบรรลุการมองเห็นคือแคชโปรโตคอลและสิ่งกีดขวางหน่วยความจำ ทัศนวิสัยทำได้ผ่านโปรโตคอลการเชื่อมโยงกันของแคชและอุปสรรคของหน่วยความจำ
จะบรรลุการซิงโครไนซ์ได้อย่างไร?
ในหนังสือของ Doug Lea "Java พร้อมกันในทางปฏิบัติ" คำอธิบายต่อไปนี้คือ:
หนังสือกล่าวถึง: โดยการรวมกฎบางอย่างของ HB การมองเห็นของตัวแปรที่ได้รับการป้องกันที่ปลดล็อคสามารถทำได้
แต่เนื่องจากเทคนิคนี้มีความอ่อนไหวต่อลำดับของคำสั่งจึงมีแนวโน้มที่จะเกิดข้อผิดพลาด
ถัดไปผู้เขียนจะสาธิตวิธีการซิงโครไนซ์ตัวแปรผ่านกฎที่ผันผวนและกฎการสั่งซื้อโปรแกรม
มามีตัวอย่างที่คุ้นเคยกันเถอะ:
คลาส ThreadPrintDemo {Static int num = 0; ธงบูลีนผันผวนแบบคงที่ = เท็จ; โมฆะคงที่สาธารณะหลัก (สตริง [] args) {เธรด t1 = เธรดใหม่ (() -> {สำหรับ (; 100> num;) {ถ้า (! flag && (num == 0 || ++ num % 2 == 0)) {system.out.println (num); flag = true;}}}); เธรด t2 = เธรดใหม่ (() -> {สำหรับ (; 100> num;) {if (flag && (++ num % 2! = 0)) {system.out.println (num); flag = false;}}}); t1.start (); t2.start (); -วัตถุประสงค์ของรหัสนี้คือการพิมพ์ตัวเลข 0 - 100 ระหว่างสองเธรด
นักเรียนที่คุ้นเคยกับการเขียนโปรแกรมพร้อมกันต้องบอกว่าตัวแปร NUM นี้ไม่ได้ใช้ความผันผวนและจะมีปัญหาการมองเห็นนั่นคือเธรด T1 ได้อัปเดต NUM และเธรด T2 ไม่สามารถรับรู้ได้
ฮ่าฮ่าผู้เขียนคิดอย่างนั้นในตอนแรก แต่เมื่อเร็ว ๆ นี้ผ่านการศึกษากฎ HB ฉันพบว่ามันโอเคที่จะลบการปรับเปลี่ยนที่ผันผวนของ NUM
มาวิเคราะห์กันแล้วโปสเตอร์ดึงรูปภาพ:
มาวิเคราะห์ตัวเลขนี้:
หมายเหตุ: กฎ HB ทำให้มั่นใจได้ว่าผลลัพธ์ของการดำเนินการก่อนหน้านี้สามารถมองเห็นได้จากการดำเนินการครั้งต่อไป
ดังนั้นในแอปเพล็ตด้านบนเธรด B ตระหนักถึงการปรับเปลี่ยนของ NUM โดยเธรด A - แม้ว่า NUM จะไม่ได้รับการแก้ไขด้วยความผันผวน
ด้วยวิธีนี้เราใช้หลักการ HB เพื่อตระหนักถึงการทำงานแบบซิงโครนัสของตัวแปรนั่นคือในสภาพแวดล้อมแบบมัลติเธรดเรามั่นใจว่าความปลอดภัยของการปรับเปลี่ยนตัวแปรที่ใช้ร่วมกันพร้อมกัน และไม่มี Java Primitives สำหรับตัวแปรนี้: ผันผวนและซิงโครไนซ์และ CAS (สมมติว่านับ)
สิ่งนี้อาจดูไม่ปลอดภัย (ปลอดภัยจริง ๆ ) และอาจดูไม่ง่ายที่จะเข้าใจ เพราะทั้งหมดนี้ถูกนำไปใช้โดยโปรโตคอลแคชและสิ่งกีดขวางหน่วยความจำใน HB พื้นฐาน
กฎอื่น ๆ เพื่อให้บรรลุการซิงโครไนซ์
การใช้งานโดยใช้กฎการเลิกจ้างเธรด:
int คงที่ a = 1; โมฆะคงที่สาธารณะหลัก (สตริง [] args) {เธรด tb = เธรดใหม่ (() -> {a = 2;}); เธรด ta = เธรดใหม่ (() -> {ลอง {tb.join ();} catch (interruptedException e) {// no} system.out.println (a);}); ta.start (); tb.start (); - ใช้กฎเริ่มต้นเธรดเพื่อนำไปใช้:
int คงที่ a = 1; โมฆะคงที่สาธารณะหลัก (สตริง [] args) {เธรด tb = เธรดใหม่ (() -> {system.out.println (a);}); เธรด ta = เธรดใหม่ (() -> {tb.start (); a = 2;}); ta.start (); -การดำเนินการทั้งสองนี้ยังสามารถตรวจสอบให้แน่ใจว่าการมองเห็นตัวแปร
มันทำลายแนวคิดก่อนหน้านี้จริงๆ ในแนวคิดก่อนหน้านี้หากตัวแปรไม่ได้รับการแก้ไขด้วยความผันผวนหรือสุดท้ายการอ่านและการเขียนภายใต้มัลติเธรดนั้นไม่ปลอดภัยอย่างแน่นอน - เพราะจะมีแคชส่งผลให้การอ่านไม่ได้ล่าสุด
อย่างไรก็ตามโดยการใช้ HB เราสามารถบรรลุเป้าหมายได้
สรุป
แม้ว่าชื่อของบทความนี้คือการตระหนักถึงการดำเนินการแบบซิงโครนัสของตัวแปรที่ใช้ร่วมกันผ่านสิ่งที่เกิดขึ้นก่อนจุดประสงค์หลักคือการเข้าใจเกิดขึ้นก่อนหน้านี้มากขึ้น การทำความเข้าใจแนวคิดที่เกิดขึ้นก่อนหน้านี้จริง ๆ แล้วเพื่อให้แน่ใจว่าความเป็นระเบียบของการดำเนินการก่อนหน้านี้ไปยังการดำเนินการครั้งต่อไปและการมองเห็นผลการดำเนินการในสภาพแวดล้อมแบบมัลติเธรด
ในเวลาเดียวกันโดยการใช้กฎสกรรมกริยาอย่างยืดหยุ่นจากนั้นการรวมกฎสองเธรดสามารถซิงโครไนซ์ - การใช้งานของตัวแปรที่ใช้ร่วมกันที่ระบุโดยไม่ต้องใช้ primitives ยังสามารถตรวจสอบการมองเห็น แม้ว่าสิ่งนี้ดูเหมือนจะไม่ง่ายต่อการอ่าน แต่ก็เป็นความพยายามเช่นกัน
Doug Lea ให้การฝึกฝนใน JUC เกี่ยวกับวิธีการรวมกฎเพื่อให้บรรลุการซิงโครไนซ์
ตัวอย่างเช่นการซิงค์คลาสด้านในของ FutureTask รุ่นเก่า (หายไป) ปรับเปลี่ยนตัวแปรระเหยง่ายผ่านวิธี tryreleaseshared และ tryacquireshared อ่านตัวแปรระเหยง่ายซึ่งใช้กฎระเหย
สิ่งนี้ใช้ประโยชน์จากกฎการสั่งซื้อของโปรแกรมโดยการตั้งค่าตัวแปรผลลัพธ์ที่ไม่ระเหยก่อนที่จะ tryreaseshared แล้วอ่านตัวแปรผลลัพธ์หลังจาก tryacquireshared
สิ่งนี้ทำให้มั่นใจได้ว่าการมองเห็นตัวแปรผลลัพธ์ คล้ายกับตัวอย่างแรกของเรา: การใช้กฎการสั่งซื้อโปรแกรมและกฎระเหยเพื่อให้ได้การมองเห็นตัวแปรปกติ
Doug Lea เองกล่าวว่าเทคโนโลยี "การใช้ความช่วยเหลือ" นี้มีแนวโน้มที่จะเกิดข้อผิดพลาดมากและควรใช้ด้วยความระมัดระวัง แต่ในบางกรณี "เลเวอเรจ" แบบนี้สมเหตุสมผลมาก
ในความเป็นจริง BlockingQueue ยัง "ใช้" กฎก่อนหน้านี้ จำกฎปลดล็อคได้หรือไม่? เมื่อปลดล็อคเกิดขึ้นองค์ประกอบภายในจะต้องมองเห็นได้
มีการดำเนินการอื่น ๆ ในห้องสมุดคลาสที่ "ใช้" หลักการที่เกิดขึ้นก่อน: คอนเทนเนอร์พร้อมกัน, Countdownlatch, Semaphore, Future, Executor, Cyclicbarrier, Exchanger ฯลฯ
ในระยะสั้นสั้น:
หลักการที่เกิดขึ้นก่อนหน้านี้คือแกนกลางของ JMM เฉพาะเมื่อมีการปฏิบัติตามหลักการ HB เท่านั้นที่สามารถสั่งซื้อและมองเห็นได้ไม่เช่นนั้นคอมไพเลอร์จะสั่งซื้อรหัสใหม่ HB ยังกำหนดกฎสำหรับการล็อคและผันผวน
โดยการผสมผสานที่เหมาะสมของกฎ HB การใช้ตัวแปรที่ใช้ร่วมกันแบบสามัญสามารถทำได้อย่างถูกต้อง
โอเคข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่าเนื้อหาของบทความนี้จะมีค่าอ้างอิงบางอย่างสำหรับการศึกษาหรือที่ทำงานของทุกคน หากคุณมีคำถามใด ๆ คุณสามารถฝากข้อความไว้เพื่อสื่อสาร ขอบคุณสำหรับการสนับสนุน Wulin.com