1. การใช้งานขั้นพื้นฐานของการซิงโครไนซ์
ซิงโครไนซ์เป็นวิธีที่ใช้กันมากที่สุดในการแก้ปัญหาพร้อมกันใน Java และวิธีที่ง่ายที่สุด ซิงโครไนซ์มีสามฟังก์ชั่นหลัก: (1) ตรวจสอบให้แน่ใจว่าเธรดร่วมกันรหัสการซิงโครไนซ์การเข้าถึงแบบเอกสิทธิ์เฉพาะบุคคล (2) ตรวจสอบให้แน่ใจว่าการปรับเปลี่ยนตัวแปรที่ใช้ร่วมกันสามารถมองเห็นได้ทันเวลา (3) แก้ปัญหาการเรียงลำดับใหม่ได้อย่างมีประสิทธิภาพ ซิงโครไนซ์มีการใช้สามครั้งของการซิงโครไนซ์:
(1) วิธีการแก้ไขปกติ
(2) แก้ไขวิธีการคงที่
(3) แก้ไขบล็อกรหัส
ต่อไปฉันจะใช้โปรแกรมตัวอย่างสองสามรายการเพื่อแสดงวิธีการใช้งานทั้งสามนี้ (เพื่อประโยชน์ในการเปรียบเทียบยกเว้นวิธีการใช้งานที่แตกต่างกันของการซิงโครไนซ์รหัสอีกสามรหัสนั้นสอดคล้องกันโดยทั่วไป)
1. ไม่มีการซิงโครไนซ์:
รหัสตัวอย่าง 1:
แพ็คเกจ com.paddx.test.concurrent; คลาสสาธารณะ SynchronizedTest {โมฆะสาธารณะวิธีการ 1 () {system.out.println ("วิธีการ 1 เริ่มต้น"); ลอง {system.out.println ("วิธีการ 1 ดำเนินการ"); Thread.sleep (3000); } catch (interruptedException e) {e.printStackTrace (); } system.out.println ("วิธีการ 1 end"); } โมฆะสาธารณะวิธีการ 2 () {system.out.println ("วิธีการ 2 เริ่ม"); ลอง {system.out.println ("วิธีการ 2 ดำเนินการ"); Thread.sleep (1,000); } catch (interruptedException e) {e.printStackTrace (); } system.out.println ("วิธีการ 2 สิ้นสุด"); } โมฆะคงที่สาธารณะหลัก (สตริง [] args) {การทดสอบขั้นสุดท้าย synchronizedTest = new SynchronizedTest (); เธรดใหม่ (ใหม่ runnable () {@Override โมฆะสาธารณะ run () {test.method1 ();}}) เริ่มต้น (); เธรดใหม่ (ใหม่ runnable () {@Override โมฆะสาธารณะ run () {test.method2 ();}}) เริ่มต้น (); -ผลการดำเนินการมีดังนี้: เธรด 1 และเธรด 2 ป้อนสถานะการดำเนินการในเวลาเดียวกัน เธรด 2 ทำงานเร็วกว่าเธรด 1 ดังนั้นเธรด 2 จะดำเนินการก่อน ในกระบวนการนี้เธรด 1 และเธรด 2 ดำเนินการในเวลาเดียวกัน
วิธีที่ 1 เริ่มต้น
วิธีที่ 1 ดำเนินการ
วิธีที่ 2 เริ่มต้น
วิธีที่ 2 ดำเนินการ
วิธีที่ 2 จบ
วิธีการที่ 1 End
2. ซิงโครไนซ์วิธีการทั่วไป:
รหัสตัวอย่างสอง:
แพ็คเกจ com.paddx.test.concurrent; คลาสสาธารณะซิงโครไนซ์เทสต์ {โมฆะโมฆะที่ซิงโครไนซ์สาธารณะ 1 () {system.out.println ("วิธีการ 1 เริ่มต้น"); ลอง {system.out.println ("วิธีการ 1 ดำเนินการ"); Thread.sleep (3000); } catch (interruptedException e) {e.printStackTrace (); } system.out.println ("วิธีการ 1 end"); } โมฆะโมฆะที่ซิงโครไนซ์สาธารณะ () {system.out.println ("วิธีการ 2 เริ่ม"); ลอง {system.out.println ("วิธีการ 2 ดำเนินการ"); Thread.sleep (1,000); } catch (interruptedException e) {e.printStackTrace (); } system.out.println ("วิธีการ 2 สิ้นสุด"); } โมฆะคงที่สาธารณะหลัก (สตริง [] args) {การทดสอบขั้นสุดท้าย synchronizedTest = new SynchronizedTest (); เธรดใหม่ (ใหม่ runnable () {@Override โมฆะสาธารณะ run () {test.method1 ();}}) เริ่มต้น (); เธรดใหม่ (ใหม่ runnable () {@Override โมฆะสาธารณะ run () {test.method2 ();}}) เริ่มต้น (); -ผลการดำเนินการมีดังนี้ หลังจากเปรียบเทียบกับเซ็กเมนต์โค้ดจะเห็นได้อย่างชัดเจนว่าเธรด 2 จำเป็นต้องรอการดำเนินการของเมธอด 1 ของเธรด 1 เพื่อให้เสร็จสมบูรณ์ก่อนที่จะเริ่มดำเนินการวิธีการ Method2
วิธีที่ 1 เริ่มต้น
วิธีที่ 1 ดำเนินการ
วิธีการที่ 1 End
วิธีที่ 2 เริ่มต้น
วิธีที่ 2 ดำเนินการ
วิธีที่ 2 จบ
3. วิธีการแบบคงที่ (คลาส) การซิงโครไนซ์
รหัสตัวอย่างที่สาม:
แพ็คเกจ com.paddx.test.concurrent; คลาสสาธารณะ SynchronizedTest {public Static Synchronized Void Method1 () {system.out.println ("วิธีการ 1 เริ่มต้น"); ลอง {system.out.println ("วิธีการ 1 ดำเนินการ"); Thread.sleep (3000); } catch (interruptedException e) {e.printStackTrace (); } system.out.println ("วิธีการ 1 end"); } public Static synchronized void method2 () {system.out.println ("วิธีการ 2 เริ่ม"); ลอง {system.out.println ("วิธีการ 2 ดำเนินการ"); Thread.sleep (1,000); } catch (interruptedException e) {e.printStackTrace (); } system.out.println ("วิธีการ 2 สิ้นสุด"); } โมฆะคงที่สาธารณะหลัก (สตริง [] args) {การทดสอบขั้นสุดท้าย synchronizedTest = new SynchronizedTest (); SynchronizedTest test2 = new SynchronizedTest (); เธรดใหม่ (ใหม่ runnable () {@Override โมฆะสาธารณะ run () {test.method1 ();}}) เริ่มต้น (); เธรดใหม่ (ใหม่ runnable () {@Override โมฆะสาธารณะ run () {test2.method2 ();}}) เริ่มต้น (); -ผลการดำเนินการมีดังนี้ การซิงโครไนซ์ของวิธีการคงที่เป็นหลักคือการซิงโครไนซ์ของคลาส (วิธีการคงที่เป็นวิธีการหลักของคลาสไม่ใช่วิธีการบนวัตถุ) ดังนั้นแม้ว่าการทดสอบและ test2 เป็นของวัตถุที่แตกต่างกันพวกเขาทั้งสองเป็นของอินสแตนซ์ของคลาส synchronizedtest ดังนั้น method1 และ method2 สามารถดำเนินการตามลำดับเท่านั้นและไม่สามารถดำเนินการพร้อมกันได้
วิธีที่ 1 เริ่มต้น
วิธีที่ 1 ดำเนินการ
วิธีการที่ 1 End
วิธีที่ 2 เริ่มต้น
วิธีที่ 2 ดำเนินการ
วิธีที่ 2 จบ
4. การซิงโครไนซ์บล็อกรหัส
รหัสตัวอย่างสี่:
แพ็คเกจ com.paddx.test.concurrent; คลาสสาธารณะ SynchronizedTest {โมฆะสาธารณะวิธีการ 1 () {system.out.println ("วิธีการ 1 เริ่มต้น"); ลอง {ซิงโครไนซ์ (นี่) {system.out.println ("วิธีการ 1 ดำเนินการ"); Thread.sleep (3000); }} catch (interruptedException e) {e.printStackTrace (); } system.out.println ("วิธีการ 1 end"); } โมฆะสาธารณะวิธีการ 2 () {system.out.println ("วิธีการ 2 เริ่ม"); ลอง {ซิงโครไนซ์ (นี่) {system.out.println ("วิธีการ 2 ดำเนินการ"); Thread.sleep (1,000); }} catch (interruptedException e) {e.printStackTrace (); } system.out.println ("วิธีการ 2 สิ้นสุด"); } โมฆะคงที่สาธารณะหลัก (สตริง [] args) {การทดสอบขั้นสุดท้าย synchronizedTest = new SynchronizedTest (); เธรดใหม่ (ใหม่ runnable () {@Override โมฆะสาธารณะ run () {test.method1 ();}}) เริ่มต้น (); เธรดใหม่ (ใหม่ runnable () {@Override โมฆะสาธารณะ run () {test.method2 ();}}) เริ่มต้น (); -ผลการดำเนินการมีดังนี้ แม้ว่าทั้งเธรด 1 และเธรด 2 ป้อนวิธีการที่สอดคล้องกันและเริ่มการดำเนินการเธรด 2 ต้องรอการดำเนินการบล็อกการซิงโครไนซ์ในเธรด 1 เพื่อให้เสร็จสมบูรณ์ก่อนที่จะป้อนบล็อกการซิงโครไนซ์
วิธีที่ 1 เริ่มต้น
วิธีที่ 1 ดำเนินการ
วิธีที่ 2 เริ่มต้น
วิธีการที่ 1 End
วิธีที่ 2 ดำเนินการ
วิธีที่ 2 จบ
2. หลักการที่ซิงโครไนซ์
หากคุณยังมีคำถามใด ๆ เกี่ยวกับผลการดำเนินการข้างต้นไม่ต้องกังวล ก่อนอื่นมาทำความเข้าใจหลักการของการซิงโครไนซ์แล้วมองย้อนกลับไปที่คำถามข้างต้นเพื่อดูอย่างรวดเร็ว ก่อนอื่นให้ดูว่ารหัสที่ซิงโครไนซ์ซิงโครไนซ์บล็อกโค้ดได้อย่างไรโดยการถอดรหัสรหัสต่อไปนี้:
แพ็คเกจ com.paddx.test.concurrent; คลาสสาธารณะ SynchronizedDemo {วิธีโมฆะสาธารณะ () {ซิงโครไนซ์ (นี่) {system.out.println ("วิธีการ 1 เริ่ม"); -ผลการสลายตัว:
เกี่ยวกับบทบาทของคำแนะนำทั้งสองนี้เราอ้างถึงคำอธิบายโดยตรงในข้อกำหนด JVM:
MonitorEnter:
แต่ละวัตถุเกี่ยวข้องกับจอภาพ จอภาพจะถูกล็อคหากมีเจ้าของ เธรดที่ดำเนินการ MonitorEnter พยายามที่จะได้รับความเป็นเจ้าของของจอภาพที่เกี่ยวข้องกับ objectref ดังต่อไปนี้: •หากจำนวนรายการของจอภาพที่เกี่ยวข้องกับ OBJECTREF เป็นศูนย์เธรดจะเข้าสู่จอภาพและตั้งจำนวนรายการเป็นหนึ่ง เธรดจะเป็นเจ้าของจอภาพ•หากเธรดเจ้าของจอภาพที่เกี่ยวข้องกับ objectref นั้นจะเข้าสู่จอภาพเพิ่มจำนวนรายการ•หากเธรดอื่นเป็นเจ้าของจอภาพที่เกี่ยวข้องกับ objectref บล็อกเธรดจนกว่าจำนวนรายการของจอภาพจะเป็นศูนย์
ความหมายทั่วไปของข้อความนี้คือ:
แต่ละวัตถุมีการล็อคจอภาพ (จอภาพ) เมื่อจอภาพถูกครอบครองมันจะถูกล็อค เมื่อเธรดดำเนินการคำสั่ง MonitorEnter จะพยายามที่จะได้รับความเป็นเจ้าของของจอภาพ กระบวนการมีดังนี้:
1. หากจำนวนรายการของจอภาพคือ 0 เธรดจะเข้าสู่จอภาพแล้วตั้งค่าหมายเลขรายการเป็น 1 เธรดเป็นเจ้าของจอภาพ
2. หากเธรดเป็นเจ้าของจอภาพแล้วและเข้ามาอีกครั้งจำนวนรายการในจอภาพจะถูกเพิ่มลงใน 1
3. หากเธรดอื่น ๆ มีการใช้งานมอนิเตอร์เธรดจะเข้าสู่สถานะการปิดกั้นจนกว่าจำนวนรายการของจอภาพคือ 0 แล้วพยายามที่จะได้รับความเป็นเจ้าของของจอภาพอีกครั้ง
MONITOREXIT:
เธรดที่ดำเนินการ monitorexit จะต้องเป็นเจ้าของจอภาพที่เกี่ยวข้องกับอินสแตนซ์ที่อ้างอิงโดย objectref เธรดลดจำนวนรายการของจอภาพที่เกี่ยวข้องกับ objectref หากเป็นผลให้ค่าของจำนวนรายการเป็นศูนย์เธรดจะออกจากจอภาพและไม่ได้เป็นเจ้าของอีกต่อไป เธรดอื่น ๆ ที่ปิดกั้นเพื่อเข้าสู่จอภาพได้รับอนุญาตให้พยายามทำเช่นนั้น
ความหมายทั่วไปของข้อความนี้คือ:
เธรดที่ดำเนินการ monitorexit จะต้องเป็นเจ้าของจอภาพที่สอดคล้องกับ objectref
เมื่อมีการดำเนินการคำสั่งจำนวนของจอภาพที่เข้ามาจะลดลง 1 ถ้าจำนวนจอภาพที่ป้อนคือ 0 หลังจากลดลง 1 เธรดจะออกจากจอภาพและไม่ได้เป็นเจ้าของจอภาพนี้อีกต่อไป เธรดอื่น ๆ ที่ถูกบล็อกโดยจอภาพนี้สามารถพยายามที่จะได้รับความเป็นเจ้าของของจอภาพนี้
ผ่านคำอธิบายทั้งสองย่อหน้านี้เราควรจะสามารถเห็นหลักการการใช้งานของการซิงโครไนซ์ได้อย่างชัดเจน ชั้นความหมายพื้นฐานของการซิงโครไนซ์เสร็จสมบูรณ์ผ่านวัตถุจอภาพ ในความเป็นจริงการรอ/แจ้งและวิธีการอื่น ๆ ก็ขึ้นอยู่กับวัตถุตรวจสอบ นี่คือเหตุผลที่วิธีการเพียงอย่างเดียวเช่นการรอ/แจ้งเตือนสามารถเรียกได้ในบล็อกหรือวิธีการซิงโครไนซ์มิฉะนั้นจะมีข้อยกเว้นของ java.lang.illegalmonitorstateException
มาดูผลลัพธ์การสลายตัวของวิธีการซิงโครไนซ์:
ซอร์สโค้ด:
แพ็คเกจ com.paddx.test.concurrent; คลาสสาธารณะซิงโครไนซ์โธด {วิธีโมฆะที่ซิงโครไนซ์สาธารณะ () {system.out.println ("Hello World!"); -ผลการสลายตัว:
เมื่อพิจารณาจากผลลัพธ์ของการสลายตัวการซิงโครไนซ์ของวิธีการไม่เสร็จสมบูรณ์ผ่านคำแนะนำ MonitorEnter และ Monitorexit (ในทางทฤษฎีก็สามารถนำไปใช้ได้ผ่านคำแนะนำทั้งสองนี้) อย่างไรก็ตามเมื่อเปรียบเทียบกับวิธีการทั่วไปตัวระบุ ACC_SYNCHRONIZED จะถูกเพิ่มเข้าไปในพูลคงที่ JVM ใช้การซิงโครไนซ์ของวิธีการตามตัวระบุนี้: เมื่อมีการเรียกวิธีการคำสั่งการโทรจะตรวจสอบว่าตั้งค่าสถานะการเข้าถึง ACC_SYNCHRONIZED ของวิธีการหรือไม่ หากตั้งค่าเธรดการดำเนินการจะได้รับการตรวจสอบก่อนจากนั้นเรียกใช้งานวิธีการหลังจากวิธีการจะดำเนินการสำเร็จ หลังจากดำเนินการวิธีการตรวจสอบจะถูกปล่อยออกมา ในระหว่างการดำเนินการวิธีการไม่มีเธรดอื่นใดที่สามารถรับวัตถุตรวจสอบเดียวกันได้อีกต่อไป ในความเป็นจริงไม่มีความแตกต่างในสาระสำคัญ แต่การซิงโครไนซ์ของวิธีการเป็นวิธีโดยนัยในการบรรลุเป้าหมายโดยไม่จำเป็นต้องทำผ่าน bytecode
3. คำอธิบายของผลการดำเนินงาน
ด้วยความเข้าใจในหลักการของการซิงโครไนซ์คุณสามารถแก้ปัญหาได้อย่างง่ายดายโดยดูที่โปรแกรมข้างต้น
1. ส่วนโค้ด 2 ผลลัพธ์:
แม้ว่า Method1 และ Method2 เป็นวิธีที่แตกต่างกัน แต่ทั้งสองวิธีจะถูกซิงโครไนซ์และถูกเรียกผ่านวัตถุเดียวกัน ดังนั้นก่อนที่จะโทรคุณต้องแข่งขันเพื่อล็อค (จอภาพ) บนวัตถุเดียวกันดังนั้นคุณจึงสามารถรับล็อคได้โดยเฉพาะเท่านั้น ดังนั้น Method1 และ Method2 สามารถดำเนินการตามลำดับเท่านั้น
2. ส่วนโค้ด 3 ผลลัพธ์:
แม้ว่าการทดสอบและการทดสอบ 2 เป็นของวัตถุที่แตกต่างกันการทดสอบและ test2 เป็นของอินสแตนซ์ที่แตกต่างกันของคลาสเดียวกัน เนื่องจาก Method1 และ Method2 ทั้งสองเป็นวิธีการซิงโครไนซ์แบบคงที่คุณต้องได้รับการตรวจสอบในคลาสเดียวกัน (แต่ละคลาสสอดคล้องกับวัตถุคลาสเดียวเท่านั้น) ดังนั้นคุณสามารถดำเนินการตามลำดับเท่านั้น
3. ส่วนโค้ด 4 ผลลัพธ์:
สำหรับการซิงโครไนซ์ของบล็อกโค้ดจำเป็นอย่างยิ่งที่จะต้องได้รับการตรวจสอบของวัตถุในวงเล็บหลังจากคำหลักที่ซิงโครไนซ์ เนื่องจากเนื้อหาของวงเล็บในรหัสนี้เป็นสิ่งนี้และ Method1 และ Method2 ถูกเรียกผ่านวัตถุเดียวกันดังนั้นก่อนที่จะป้อนบล็อกการซิงโครไนซ์คุณต้องแข่งขันเพื่อล็อคบนวัตถุเดียวกันดังนั้นบล็อกการซิงโครไนซ์สามารถดำเนินการตามลำดับเท่านั้น
สี่สรุป
ซิงโครไนซ์เป็นวิธีที่ใช้กันมากที่สุดสำหรับความปลอดภัยของเธรดในการเขียนโปรแกรม Java พร้อมกันและใช้งานได้ง่าย อย่างไรก็ตามหากเราสามารถเข้าใจหลักการในเชิงลึกและมีความเข้าใจเกี่ยวกับความรู้พื้นฐานเช่นล็อคการตรวจสอบมันสามารถช่วยให้เราใช้คำหลักที่ซิงโครไนซ์ได้อย่างถูกต้องและในทางกลับกันก็สามารถช่วยให้เราเข้าใจกลไกการเขียนโปรแกรมพร้อมกันได้ดีขึ้น นอกจากนี้คุณยังสามารถจัดการกับปัญหาที่เกิดขึ้นพร้อมกันได้อย่างสงบในชีวิตประจำวัน