1. ปัญหาความปลอดภัยของด้ายจะเกิดขึ้นเมื่อใด
จะไม่มีปัญหาด้านความปลอดภัยของเธรดในเธรดเดียว แต่ในการเขียนโปรแกรมแบบหลายเธรดคุณสามารถเข้าถึงทรัพยากรเดียวกันได้ในเวลาเดียวกัน ทรัพยากรนี้อาจเป็นทรัพยากรประเภทต่าง ๆ : ตัวแปรวัตถุไฟล์ตารางฐานข้อมูล ฯลฯ เมื่อหลายเธรดเข้าถึงทรัพยากรเดียวกันในเวลาเดียวกันจะมีปัญหา:
เนื่องจากกระบวนการที่ดำเนินการโดยแต่ละเธรดนั้นไม่สามารถควบคุมได้จึงเป็นไปได้ว่าผลลัพธ์สุดท้ายจะตรงกันข้ามกับความปรารถนาจริงหรือจะนำไปสู่ข้อผิดพลาดของโปรแกรมโดยตรง
ยกตัวอย่างง่ายๆ:
ขณะนี้มีสองเธรดที่อ่านข้อมูลจากเครือข่ายแยกต่างหากจากนั้นแทรกลงในตารางฐานข้อมูลซึ่งต้องการข้อมูลที่ซ้ำกันไม่สามารถแทรกได้
จากนั้นจะต้องมีการดำเนินการสองครั้งในกระบวนการแทรกข้อมูล:
1) ตรวจสอบว่าข้อมูลมีอยู่ในฐานข้อมูลหรือไม่
2) ถ้ามีอยู่มันจะไม่ถูกแทรก; หากไม่มีอยู่มันจะถูกแทรกลงในฐานข้อมูล
หากสองเธรดจะถูกแทนด้วย Thread-1 และ Thread-2 ตามลำดับและในบางจุดเธรด -1 และเธรด -2 ทั้งสองอ่านข้อมูล X สิ่งนี้อาจเกิดขึ้น:
Thread-1 ตรวจสอบว่า Data X มีอยู่ในฐานข้อมูลหรือไม่และเธรด 2 ยังตรวจสอบว่า Data X มีอยู่ในฐานข้อมูลหรือไม่
เป็นผลให้ผลลัพธ์ของการตรวจสอบสองเธรดคือ Data X ไม่มีอยู่ในฐานข้อมูลดังนั้นเธรดทั้งสองจะแทรกข้อมูล X ลงในตารางฐานข้อมูลตามลำดับ
นี่เป็นปัญหาด้านความปลอดภัยของเธรดนั่นคือเมื่อหลายเธรดเข้าถึงทรัพยากรในเวลาเดียวกันผลลัพธ์การรันโปรแกรมไม่ใช่ผลลัพธ์ที่คุณต้องการเห็น
ที่นี่ทรัพยากรนี้เรียกว่า: ทรัพยากรที่สำคัญ (หรือที่เรียกว่าทรัพยากรที่ใช้ร่วมกัน)
กล่าวคือเมื่อเธรดหลายเธรดเข้าถึงทรัพยากรที่สำคัญ (วัตถุหนึ่งแอตทริบิวต์ในวัตถุไฟล์ฐานข้อมูล ฯลฯ ) ปัญหาความปลอดภัยของเธรดอาจเกิดขึ้น
อย่างไรก็ตามเมื่อหลายเธรดดำเนินการวิธีการตัวแปรท้องถิ่นภายในวิธีการไม่ใช่ทรัพยากรที่สำคัญเนื่องจากวิธีการดังกล่าวจะถูกดำเนินการบนสแต็กในขณะที่สแต็ก Java เป็นเธรดส่วนตัวดังนั้นจะไม่มีปัญหาด้านความปลอดภัยของเธรด
2. จะแก้ปัญหาความปลอดภัยของด้ายได้อย่างไร?
โดยทั่วไปวิธีแก้ปัญหาความปลอดภัยของด้าย?
โดยพื้นฐานแล้วเมื่อแก้ปัญหาความปลอดภัยของเธรดโหมดพร้อมกันทั้งหมดใช้โซลูชัน "การเข้าถึงทรัพยากรที่สำคัญ" ซึ่งในเวลาเดียวกันมีเพียงหนึ่งเธรดเดียวเท่านั้นที่สามารถเข้าถึงทรัพยากรที่สำคัญหรือที่เรียกว่าการเข้าถึงแบบซิงโครนัสร่วมกัน
โดยทั่วไปแล้วจะมีการเพิ่มล็อคก่อนรหัสที่เข้าถึงทรัพยากรที่สำคัญ หลังจากเข้าถึงทรัพยากรที่สำคัญแล้วล็อคจะถูกปล่อยออกมาและเธรดอื่น ๆ ยังคงเข้าถึงได้
ใน Java มีสองวิธีในการใช้การเข้าถึง mutex แบบซิงโครนัส: ซิงโครไนซ์และล็อค
บทความนี้ส่วนใหญ่พูดถึงการใช้การซิงโครไนซ์และการใช้การล็อคอธิบายไว้ในโพสต์บล็อกถัดไป
สามวิธีการซิงโครไนซ์แบบซิงโครไนซ์หรือบล็อกการซิงโครไนซ์
ก่อนที่จะเข้าใจการใช้คำหลักที่ซิงโครไนซ์ให้ดูแนวคิดก่อน: ล็อค mutex ตามชื่อแนะนำ: ล็อคที่สามารถบรรลุวัตถุประสงค์ของการเข้าถึง mutex
เพื่อให้ตัวอย่างง่ายๆ: หากมีการเพิ่มทรัพยากรที่สำคัญลงใน mutex เมื่อเธรดหนึ่งเข้าถึงทรัพยากรที่สำคัญเธรดอื่น ๆ สามารถรอได้เท่านั้น
ใน Java แต่ละวัตถุมีเครื่องหมายล็อค (จอภาพ) หรือที่เรียกว่าจอภาพ เมื่อหลายเธรดเข้าถึงวัตถุในเวลาเดียวกันเธรดสามารถเข้าถึงได้ก็ต่อเมื่อได้รับการล็อคของวัตถุ
ใน Java คำหลักที่ซิงโครไนซ์สามารถใช้เพื่อทำเครื่องหมายวิธีการหรือบล็อกรหัส เมื่อเธรดเรียกวิธีการซิงโครไนซ์ของวัตถุหรือเข้าถึงบล็อกรหัสที่ซิงโครไนซ์เธรดจะได้รับการล็อคของวัตถุ เธรดอื่นไม่สามารถเข้าถึงวิธีการในขณะนี้ เฉพาะเมื่อมีการดำเนินการเมธอดหรือบล็อกรหัสจะถูกเรียกใช้งานเธรดจะปล่อยล็อคของวัตถุและเธรดอื่น ๆ สามารถเรียกใช้เมธอดหรือบล็อกรหัส
ต่อไปนี้เป็นตัวอย่างง่ายๆเพื่อแสดงให้เห็นถึงการใช้คำหลักที่ซิงโครไนซ์:
1. วิธีการซิงโครไนซ์
ในรหัสต่อไปนี้สองเธรดเรียกวัตถุ InsertData เพื่อแทรกข้อมูล:
การทดสอบระดับสาธารณะ {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {final insertData insertData = new insertData (); เธรดใหม่ () {โมฆะสาธารณะเรียกใช้ () {insertData.insert (thread.currentthread ()); - }.เริ่ม(); เธรดใหม่ () {โมฆะสาธารณะเรียกใช้ () {insertData.insert (thread.currentthread ()); - }.เริ่ม(); }} คลาส InsertData {Private ArrayList <Integer> arrayList = new ArrayList <integer> (); การแทรกโมฆะสาธารณะ (เธรดเธรด) {สำหรับ (int i = 0; i <5; i ++) {system.out.println (thread.getName ()+"แทรกข้อมูล"+i); arraylist.add (i); - ในเวลานี้ผลลัพธ์ผลลัพธ์ของโปรแกรมคือ:
นี่แสดงให้เห็นว่าสองเธรดดำเนินการวิธีการแทรกในเวลาเดียวกัน
หากเพิ่มคำหลักที่ซิงโครไนซ์ก่อนที่วิธีการแทรกผลการรันคือ:
คลาส InsertData {Private ArrayList <Integer> arrayList = new ArrayList <integer> (); การแทรกโมฆะแบบซิงโครไนซ์สาธารณะ (เธรดเธรด) {สำหรับ (int i = 0; i <5; i ++) {system.out.println (thread.getName ()+"แทรกข้อมูล"+i); arraylist.add (i); -จากเอาต์พุตข้างต้นข้อมูลที่แทรกในเธรด -1 จะดำเนินการเฉพาะหลังจากแทรกเธรด -0 สิ่งนี้แสดงให้เห็นว่าเธรด -0 และเธรด -1 ดำเนินการวิธีการแทรกตามลำดับ
นี่คือวิธีการซิงโครไนซ์
อย่างไรก็ตามมีบางจุดที่ควรทราบ:
1) เมื่อเธรดกำลังเข้าถึงวิธีการซิงโครไนซ์ของวัตถุเธรดอื่น ๆ ไม่สามารถเข้าถึงวิธีการซิงโครไนซ์อื่น ๆ ของวัตถุ เหตุผลนี้ง่ายมากเพราะวัตถุมีเพียงล็อคเดียว เมื่อเธรดได้รับการล็อคของวัตถุเธรดอื่น ๆ ไม่สามารถรับการล็อคของวัตถุได้ดังนั้นพวกเขาจึงไม่สามารถเข้าถึงวิธีการซิงโครไนซ์อื่น ๆ ของวัตถุ
2) เมื่อเธรดเข้าถึงวิธีการซิงโครไนซ์ของวัตถุแล้วเธรดอื่น ๆ สามารถเข้าถึงวิธีที่ไม่ซิงโครไนซ์ของวัตถุ เหตุผลนี้ง่ายมาก การเข้าถึงวิธีการที่ไม่ได้ซิงโครไนซ์ไม่จำเป็นต้องล็อควัตถุ หากวิธีการไม่ได้รับการแก้ไขด้วยคำหลักที่ซิงโครไนซ์หมายความว่าจะไม่ใช้ทรัพยากรที่สำคัญดังนั้นเธรดอื่น ๆ ก็สามารถเข้าถึงวิธีนี้ได้
3) หากเธรด A จำเป็นต้องเข้าถึงวิธีที่ซิงโครไนซ์ Fun1 ของ Object Object1 และเธรด B อื่นจำเป็นต้องเข้าถึงวิธีการที่ซิงโครไนซ์ Fun1 ของ Object Object2 แม้ว่า Object1 และ Object2 จะเป็นประเภทเดียวกัน) จะไม่มีปัญหาด้านความปลอดภัยของเธรด
2. บล็อกรหัสที่ซิงโครไนซ์
บล็อกรหัสที่ซิงโครไนซ์คล้ายกับแบบฟอร์มต่อไปนี้:
ซิงโครไนซ์ (syncobject) {
-
เมื่อบล็อกของรหัสนี้ถูกเรียกใช้ในเธรดเธรดจะได้รับการล็อคของวัตถุ synobject ทำให้เป็นไปไม่ได้ที่เธรดอื่น ๆ จะเข้าถึงบล็อกของรหัสในเวลาเดียวกัน
Synobject อาจเป็นสิ่งนี้แทนการล็อคที่ได้รับวัตถุปัจจุบันหรืออาจเป็นแอตทริบิวต์ในคลาสซึ่งเป็นตัวแทนของล็อคที่ได้รับแอตทริบิวต์
ตัวอย่างเช่นวิธีการแทรกด้านบนสามารถเปลี่ยนเป็นสองรูปแบบต่อไปนี้:
คลาส InsertData {Private ArrayList <Integer> arrayList = new ArrayList <integer> (); การแทรกโมฆะสาธารณะ (เธรดเธรด) {ซิงโครไนซ์ (นี่) {สำหรับ (int i = 0; i <100; i ++) {system.out.println (thread.getName ()+"แทรกข้อมูล"+i); arraylist.add (i); }}}} คลาส InsertData {Private ArrayList <Integer> arrayList = new ArrayList <integer> (); วัตถุวัตถุส่วนตัว = วัตถุใหม่ (); การแทรกโมฆะสาธารณะ (เธรดเธรด) {ซิงโครไนซ์ (วัตถุ) {สำหรับ (int i = 0; i <100; i ++) {system.out.println (thread.getName ()+"แทรกข้อมูล"+i); arraylist.add (i); -ดังที่เห็นได้จากด้านบนบล็อกโค้ดที่ซิงโครไนซ์มีความยืดหยุ่นในการใช้งานมากกว่าวิธีการซิงโครไนซ์ เนื่องจากอาจมีเพียงส่วนหนึ่งของรหัสในวิธีการที่จะต้องซิงโครไนซ์หากวิธีการทั้งหมดถูกซิงโครไนซ์ในเวลานี้มันจะส่งผลกระทบต่อประสิทธิภาพการดำเนินการของโปรแกรม ปัญหานี้สามารถหลีกเลี่ยงได้โดยใช้บล็อกรหัสที่ซิงโครไนซ์ บล็อกรหัสที่ซิงโครไนซ์สามารถซิงโครไนซ์เมื่อต้องการการซิงโครไนซ์เท่านั้น
นอกจากนี้แต่ละชั้นเรียนยังมีล็อคซึ่งสามารถใช้เพื่อควบคุมการเข้าถึงสมาชิกข้อมูลแบบคงที่พร้อมกัน
และหากเธรดดำเนินการวิธีการที่ไม่ได้อยู่ในระบบแบบคงที่ของวัตถุและเธรดอื่นจำเป็นต้องดำเนินการวิธีการซิงโครไนซ์แบบคงที่ของคลาสที่เป็นของวัตถุจะไม่มีการยกเว้นซึ่งกันและกันในเวลานี้
คุณจะเข้าใจโดยดูที่รหัสต่อไปนี้:
การทดสอบระดับสาธารณะ {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {final insertData insertData = new insertData (); เธรดใหม่ () {@Override โมฆะสาธารณะเรียกใช้ () {insertData.Insert (); } }.เริ่ม(); เธรดใหม่ () {@Override โมฆะสาธารณะเรียกใช้ () {insertData.insert1 (); } }.เริ่ม(); }} คลาส InsertData {โมฆะที่ซิงโครไนซ์สาธารณะซิงโครไนซ์แทรก () {system.out.println ("ดำเนินการแทรก"); ลอง {thread.sleep (5000); } catch (interruptedException e) {e.printStackTrace (); } system.out.println ("ดำเนินการแทรก 1"); } สาธารณะที่ซิงโครไนซ์แบบคงที่ INSERT1 () {System.out.println ("Execute Insert1"); System.out.println ("ดำเนินการแทรก 1"); - ผลการดำเนินการ;
วิธีการแทรกถูกดำเนินการในเธรดแรกซึ่งจะไม่ทำให้เธรดที่สองเพื่อบล็อกวิธีการแทรก 1
ลองมาดูกันว่าคำหลักที่ซิงโครไนซ์ทำอะไร เรามาคอมไพล์ bytecode ของมันกันเถอะ ไบต์ที่ถอดรหัสของรหัสต่อไปนี้คือ:
คลาสสาธารณะ insertData {วัตถุส่วนตัว = วัตถุใหม่ (); การแทรกโมฆะสาธารณะ (เธรดเธรด) {ซิงโครไนซ์ (วัตถุ) {}} void synchronized public synchronized insert1 (เธรดเธรด) {} void public void insert2 (เธรดเธรด) {}}จาก bytecode ที่ได้จากการถอดรหัสจะเห็นได้ว่าบล็อกรหัสที่ซิงโครไนซ์มีสองคำแนะนำ: MonitorEnter และ Monitorexit เมื่อคำสั่ง MonitorEnter ถูกดำเนินการจำนวนล็อคของวัตถุจะเพิ่มขึ้น 1 ในขณะที่เมื่อคำสั่ง monitorexit ถูกดำเนินการจำนวนล็อคของวัตถุจะลดลง 1 ในความเป็นจริงสิ่งนี้คล้ายกับการดำเนินการ PV ในระบบปฏิบัติการ การดำเนินการ PV ในระบบปฏิบัติการใช้เพื่อควบคุมหลายเธรดเพื่อเข้าถึงทรัพยากรที่สำคัญ สำหรับวิธีการซิงโครไนซ์เธรดในการดำเนินการจะรับรู้ว่าโครงสร้าง method_info ของวิธีการมีการตั้งค่าการตั้งค่าสถานะ ACC_SYNCHRONIZED หรือไม่จากนั้นจะได้รับการล็อคของวัตถุโดยอัตโนมัติเรียกวิธีการและในที่สุดก็ปล่อยล็อค หากมีข้อยกเว้นเกิดขึ้นเธรดจะปล่อยล็อคโดยอัตโนมัติ
สิ่งหนึ่งที่ควรทราบ: สำหรับวิธีการซิงโครไนซ์หรือบล็อกรหัสที่ซิงโครไนซ์เมื่อมีข้อยกเว้นเกิดขึ้น JVM จะปล่อยล็อคที่ถูกครอบครองโดยเธรดปัจจุบันโดยอัตโนมัติดังนั้นจะไม่มีการหยุดชะงักเนื่องจากข้อยกเว้น
3. สิ่งที่น่าสังเกตอื่น ๆ เกี่ยวกับการซิงโครไนซ์
1. ความแตกต่างระหว่างซิงโครไนซ์และซิงโครไนซ์แบบคงที่
ซิงโครไนซ์ล็อคอินสแตนซ์ปัจจุบันของคลาสเพื่อป้องกันไม่ให้เธรดอื่นเข้าถึงบล็อกที่ซิงโครไนซ์ทั้งหมดของอินสแตนซ์ของคลาสในเวลาเดียวกัน โปรดทราบว่านี่คือ "อินสแตนซ์ปัจจุบันของคลาส" และไม่มีข้อ จำกัด ดังกล่าวในสองอินสแตนซ์ที่แตกต่างกันของคลาส จากนั้นซิงโครไนซ์แบบคงที่เกิดขึ้นเพื่อควบคุมการเข้าถึงอินสแตนซ์ทั้งหมดของชั้นเรียน แบบซิงโครไนซ์แบบคงที่ จำกัด เธรดเพื่อเข้าถึงอินสแตนซ์ทั้งหมดของคลาสใน JVM ในเวลาเดียวกันและเข้าถึงรหัสที่เกี่ยวข้องได้อย่างรวดเร็ว ในความเป็นจริงหากมีการซิงโครไนซ์ในเมธอดหรือบล็อกรหัสในคลาสจากนั้นหลังจากสร้างอินสแตนซ์ของคลาสนี้คลาสจะมีการตรวจสอบอย่างรวดเร็วและเธรดการเข้าถึงพร้อมกันของอินสแตนซ์ที่ถูกแก้ไขจะได้รับการป้องกันอย่างรวดเร็ว การซิงโครไนซ์แบบคงที่เป็นสาธารณะหนึ่งในการตรวจสอบซึ่งเป็นความแตกต่างระหว่างทั้งสอง นั่นคือการซิงโครไนซ์นั้นเทียบเท่ากับสิ่งนี้ synchronized และการซิงโครไนซ์แบบคงที่เทียบเท่ากับบางสิ่งบางอย่าง synchronized
นักเขียนชาวญี่ปุ่นคนหนึ่งของ Jie Chenghao "Java Multithreaded Design Pattern" มีคอลัมน์เช่นนี้:
Pulbic class something () {public synchronized void issynca () {} public synchronized void issyncb () {} public static synchronized csynca () {} โมฆะ cyncb () {}} {}} {}} จากนั้นหากมีการเพิ่มสองอินสแตนซ์ A และ B ของคลาสบางสิ่งบางอย่างทำไมกลุ่มของวิธีการต่อไปนี้สามารถเข้าถึงได้พร้อมกันมากกว่าหนึ่งเธรด? Axissynca () และ X.ISSYNCB ()
bxissynca () และ y.issynca ()
cxcsynca () และ y.csyncb ()
dxissynca () และบางสิ่งบางอย่าง csynca ()
ที่นี่เป็นที่ชัดเจนว่าสามารถตัดสินได้:
A ทั้งการเข้าถึงโดเมนที่ซิงโครไนซ์ของอินสแตนซ์เดียวกันดังนั้นจึงไม่สามารถเข้าถึงได้ในเวลาเดียวกัน B ใช้สำหรับอินสแตนซ์ที่แตกต่างกันดังนั้นจึงสามารถเข้าถึงได้ในเวลาเดียวกัน เนื่องจากมีการซิงโครไนซ์แบบคงที่อินสแตนซ์ที่แตกต่างกันจะยังคงถูก จำกัด ซึ่งเทียบเท่ากับบางสิ่งบางอย่าง issynca () และบางสิ่งบางอย่าง issyncb () ดังนั้นจึงไม่สามารถเข้าถึงได้ในเวลาเดียวกัน
แล้ว D แล้ว D?, คำตอบในหนังสือสามารถเข้าถึงได้พร้อมกัน เหตุผลสำหรับคำตอบคือซิงโครไนซ์คือวิธีการอินสแตนซ์และวิธีการคลาสซิงโครไนซ์นั้นแตกต่างจากการล็อค
การวิเคราะห์ส่วนบุคคลหมายความว่าการซิงโครไนซ์แบบซิงโครไนซ์และแบบคงที่เทียบเท่ากับแก๊งสองแก๊งซึ่งแต่ละตัวมีการควบคุมของตัวเองและไม่มีข้อ จำกัด ซึ่งกันและกันและสามารถเข้าถึงได้ในเวลาเดียวกัน ยังไม่ชัดเจนว่านำไปใช้ในการออกแบบภายใน Java อย่างไร
บทสรุป: A: แบบคงที่แบบซิงโครไนซ์เป็นขอบเขตของคลาสที่แน่นอน ซิงโครไนซ์ csync {} ป้องกันหลายเธรดจากการเข้าถึงวิธีการคงที่แบบซิงโครไนซ์ในคลาสนี้ในเวลาเดียวกัน มันทำงานกับทุกอินสแตนซ์วัตถุของคลาส
B: ซิงโครไนซ์เป็นขอบเขตของอินสแตนซ์ ซิงโครไนซ์ ISSYNC () {} ป้องกันหลายเธรดจากการเข้าถึงวิธีการซิงโครไนซ์ในอินสแตนซ์นี้ในเวลาเดียวกัน
2. ความแตกต่างระหว่างวิธีการซิงโครไนซ์และรหัสที่ซิงโครไนซ์อย่างรวดเร็ว
ไม่มีความแตกต่างระหว่างวิธีการซิงโครไนซ์ () {} และซิงโครไนซ์ (สิ่งนี้) {} แต่วิธีการซิงโครไนซ์ () {} สะดวกสำหรับการอ่านความเข้าใจในขณะที่ซิงโครไนซ์
3. คำหลักที่ซิงโครไนซ์ไม่สามารถสืบทอดได้