ในการพัฒนาทั่วไปผู้เขียนมักจะเห็นว่านักเรียนหลายคนใช้วิธีการพื้นฐานบางอย่างในการรักษารูปแบบการพัฒนาที่เกิดขึ้นพร้อมกันของ Java ตัวอย่างเช่นระเหยได้ซิงโครไนซ์ แพ็คเกจที่เกิดขึ้นพร้อมกันขั้นสูงเช่นล็อคและอะตอมมักไม่ได้ใช้งานโดยคนจำนวนมาก ฉันคิดว่าเหตุผลส่วนใหญ่เกิดจากการขาดคุณลักษณะของหลักการ ในงานพัฒนาที่วุ่นวายใครสามารถเข้าใจและใช้โมเดลพร้อมกันที่ถูกต้องได้อย่างถูกต้อง
เมื่อเร็ว ๆ นี้ตามความคิดนี้ฉันวางแผนที่จะจัดระเบียบกลไกการควบคุมพร้อมกันลงในบทความ มันไม่ได้เป็นเพียงความทรงจำของความรู้ของคุณเอง แต่ยังหวังว่าเนื้อหาที่กล่าวถึงในบทความนี้สามารถช่วยนักพัฒนาส่วนใหญ่ได้
การพัฒนาโปรแกรมแบบขนานย่อมเกี่ยวข้องกับปัญหาต่าง ๆ เช่นการทำงานร่วมกันแบบมัลติเธรดและการทำงานร่วมกันแบบหลายงานและการแบ่งปันข้อมูล ใน JDK มีหลายวิธีในการควบคุมการควบคุมพร้อมกันระหว่างหลายเธรด ตัวอย่างเช่นใช้กันทั่วไป: ล็อคภายใน, ล็อคใหม่, ล็อคอ่าน-เขียนและบันทึก
โมเดลหน่วยความจำ Java
ใน Java แต่ละเธรดมีพื้นที่หน่วยความจำที่ใช้งานได้ซึ่งเก็บสำเนาของค่าของตัวแปรในหน่วยความจำหลักที่แชร์โดยเธรดทั้งหมด เมื่อเธรดดำเนินการมันจะทำงานตัวแปรเหล่านี้ในหน่วยความจำการทำงานของตัวเอง
ในการเข้าถึงตัวแปรที่ใช้ร่วมกันเธรดมักจะได้รับการล็อคและล้างพื้นที่หน่วยความจำที่ทำงานซึ่งทำให้มั่นใจได้ว่าตัวแปรที่ใช้ร่วมกันถูกโหลดอย่างถูกต้องจากพื้นที่หน่วยความจำที่ใช้ร่วมกันของเธรดทั้งหมดในพื้นที่หน่วยความจำการทำงานของเธรด เมื่อเธรดปลดล็อคค่าของตัวแปรในพื้นที่หน่วยความจำการทำงานจะรับประกันว่าจะเชื่อมโยงกับหน่วยความจำที่ใช้ร่วมกัน
เมื่อเธรดใช้ตัวแปรที่แน่นอนไม่ว่าโปรแกรมจะใช้การดำเนินการซิงโครไนซ์เธรดอย่างถูกต้องค่าที่ได้รับจะต้องเป็นค่าที่เก็บไว้ในตัวแปรด้วยตัวเองหรือเธรดอื่น ๆ ตัวอย่างเช่นหากเธรดสองตัวเก็บค่าที่แตกต่างกันหรือการอ้างอิงวัตถุลงในตัวแปรที่ใช้ร่วมกันเดียวกันค่าของตัวแปรนั้นมาจากเธรดนี้หรือจากเธรดนั้นและค่าของตัวแปรที่ใช้ร่วมกันจะไม่ได้รับค่าอ้างอิงของสองเธรด
ที่อยู่ที่โปรแกรม Java สามารถเข้าถึงได้เมื่อใช้ตัวแปร ไม่เพียง แต่รวมถึงตัวแปรประเภทพื้นฐานและตัวแปรประเภทอ้างอิงเท่านั้น แต่ยังรวมถึงตัวแปรประเภทอาร์เรย์ด้วย ตัวแปรที่เก็บไว้ในพื้นที่หน่วยความจำหลักสามารถแชร์ได้โดยเธรดทั้งหมด แต่เป็นไปไม่ได้ที่เธรดหนึ่งจะเข้าถึงพารามิเตอร์หรือตัวแปรท้องถิ่นของเธรดอื่นดังนั้นนักพัฒนาไม่ต้องกังวลเกี่ยวกับปัญหาความปลอดภัยของเธรดของตัวแปรท้องถิ่น
ตัวแปรระเหยง่ายสามารถเห็นได้ระหว่างหลายเธรด
เนื่องจากแต่ละเธรดมีพื้นที่หน่วยความจำที่ทำงานของตัวเองจึงอาจมองไม่เห็นกับเธรดอื่น ๆ เมื่อเธรดหนึ่งเปลี่ยนข้อมูลหน่วยความจำการทำงานของตัวเอง ในการทำเช่นนี้คุณสามารถใช้คำหลักที่ผันผวนเพื่อทำลายเธรดทั้งหมดเพื่ออ่านและเขียนตัวแปรในหน่วยความจำเพื่อให้ตัวแปรผันผวนสามารถมองเห็นได้ในหลายเธรด
ตัวแปรที่ประกาศเป็นความผันผวนสามารถรับประกันได้ดังนี้:
1. การดัดแปลงตัวแปรโดยเธรดอื่น ๆ สามารถสะท้อนได้ทันทีในเธรดปัจจุบัน
2. ตรวจสอบให้แน่ใจว่าการปรับเปลี่ยนเธรดปัจจุบันของตัวแปรผันผวนสามารถเขียนกลับไปยังหน่วยความจำที่ใช้ร่วมกันในเวลาและมองเห็นโดยเธรดอื่น ๆ
3. ใช้ตัวแปรที่ประกาศโดยผันผวนและคอมไพเลอร์จะรับรองความเป็นระเบียบเรียบร้อย
คำหลักที่ซิงโครไนซ์
คำหลักที่ซิงโครไนซ์ซิงโครไนซ์เป็นหนึ่งในวิธีการซิงโครไนซ์ที่ใช้กันมากที่สุดในภาษา Java ในรุ่น JDK รุ่นแรกประสิทธิภาพของ Synchronized นั้นไม่ค่อยดีนักและค่าที่เหมาะสมสำหรับโอกาสที่การแข่งขันล็อคไม่ได้รุนแรงเป็นพิเศษ ใน JDK6 ช่องว่างระหว่างล็อคที่ซิงโครไนซ์และไม่เป็นธรรมได้แคบลง ที่สำคัญกว่านั้นการซิงโครไนซ์นั้นกระชับและชัดเจนมากขึ้นและรหัสสามารถอ่านและบำรุงรักษาได้
วิธีการล็อควัตถุ:
โมฆะแบบซิงโครไนซ์สาธารณะ () {}
เมื่อมีการเรียกเมธอด () เมธอดเธรดการเรียกจะต้องได้รับวัตถุปัจจุบันก่อน หากล็อควัตถุปัจจุบันถูกจับโดยเธรดอื่นเธรดการโทรจะรอ หลังจากการละเมิดสิ้นสุดลงการล็อควัตถุจะถูกปล่อยออกมา วิธีการข้างต้นเทียบเท่ากับวิธีการเขียนต่อไปนี้:
วิธีโมฆะสาธารณะ () {ซิงโครไนซ์ (นี่) {// ทำอะไรบางอย่าง…}} ประการที่สองการซิงโครไนซ์ยังสามารถใช้ในการสร้างบล็อกการซิงโครไนซ์ เมื่อเปรียบเทียบกับวิธีการซิงโครไนซ์บล็อกการซิงโครไนซ์สามารถควบคุมช่วงรหัสการซิงโครไนซ์ได้อย่างแม่นยำยิ่งขึ้น รหัสการซิงโครไนซ์ขนาดเล็กนั้นเร็วมากเข้าและออกจากล็อคจึงทำให้ระบบมีปริมาณงานที่สูงขึ้น
วิธีโมฆะสาธารณะ (Object O) {// beforesynchronized (O) {// ทำอะไรบางอย่าง ... } // หลังจาก} ซิงโครไนซ์ยังสามารถใช้สำหรับฟังก์ชั่นคงที่:
โมฆะแบบคงที่แบบซิงโครไนซ์สาธารณะ () {}
เป็นสิ่งสำคัญที่จะต้องทราบในสถานที่นี้ว่าการล็อคที่ซิงโครไนซ์จะถูกเพิ่มลงในวัตถุคลาสปัจจุบันดังนั้นการเรียกใช้วิธีนี้ทั้งหมดไปยังวิธีนี้จะต้องได้รับการล็อคของวัตถุคลาส
แม้ว่าการซิงโครไนซ์สามารถรับรองความปลอดภัยของด้ายของวัตถุหรือส่วนรหัสการใช้การซิงโครไนซ์เพียงอย่างเดียวยังไม่เพียงพอที่จะควบคุมการโต้ตอบเธรดด้วยตรรกะที่ซับซ้อน เพื่อให้เกิดการโต้ตอบระหว่างหลายเธรดต้องใช้วิธีการรอ () และแจ้ง () ของวัตถุของวัตถุ
การใช้งานทั่วไป:
ซิงโครไนซ์ (obj) {ในขณะที่ (<?>) {obj.wait (); // ดำเนินการต่อเพื่อดำเนินการหลังจากได้รับการแจ้งเตือน - ก่อนใช้วิธีการรอ () คุณต้องได้รับการล็อควัตถุ เมื่อมีการดำเนินการวิธีการรอ () เธรดปัจจุบันอาจปล่อยล็อคแบบพิเศษของ OBJ เพื่อใช้งานโดยเธรดอื่น
เมื่อรอเธรดบน OBJ เพื่อรับ obj.notify () มันสามารถฟื้นล็อคพิเศษของ OBJ และทำงานต่อไป โปรดทราบว่าวิธีการแจ้งเตือน () คือการสุ่มทำให้เธรดรอบนวัตถุปัจจุบัน
นี่คือการใช้คิวการบล็อก:
Public Class Blockqueue {รายการส่วนตัว = new ArrayList (); วัตถุที่ซิงโครไนซ์สาธารณะป๊อป () พ่น InterruptedException {ในขณะที่ (list.size () == 0) {this.wait (); } if (list.size ()> 0) {return list.remove (0); } else {return null; }} วัตถุที่ซิงโครไนซ์สาธารณะใส่ (object obj) {list.add (obj); this.notify (); - ซิงโครไนซ์และรอ () และแจ้ง () ควรเป็นทักษะพื้นฐานที่นักพัฒนา Java ต้องเชี่ยวชาญ
reentrantlock reentrantlock ล็อค
Reentrantlock เรียกว่า reentrantlock มันมีคุณสมบัติที่ทรงพลังมากกว่าการซิงโครไนซ์มันสามารถขัดจังหวะและเวลา ในกรณีของการพร้อมกันสูงมันมีข้อได้เปรียบด้านประสิทธิภาพที่ชัดเจนมากกว่าการซิงโครไนซ์
Reentrantlock ให้ทั้งล็อคที่เป็นธรรมและไม่เป็นธรรม การล็อคที่เป็นธรรมคือการล็อคครั้งแรกในครั้งแรกและไม่สามารถล็อคล็อคที่เป็นธรรมได้ แน่นอนจากมุมมองของประสิทธิภาพประสิทธิภาพของการล็อคที่ไม่เป็นธรรมนั้นดีกว่ามาก ดังนั้นในกรณีที่ไม่มีความต้องการพิเศษควรเป็นที่ต้องการล็อคที่ไม่เป็นธรรม แต่การซิงโครไนซ์ให้อุตสาหกรรมล็อคไม่ยุติธรรมอย่างแน่นอน Reentrantlock สามารถระบุได้ว่าการล็อคนั้นยุติธรรมเมื่อสร้าง
เมื่อใช้ล็อคใหม่ให้แน่ใจว่าได้ปล่อยล็อคที่ส่วนท้ายของโปรแกรม โดยทั่วไปรหัสสำหรับการปล่อยล็อคจะต้องเขียนในที่สุด มิฉะนั้นหากข้อยกเว้นของโปรแกรมเกิดขึ้น LOACK จะไม่ถูกปล่อยออกมา ล็อคที่ซิงโครไนซ์จะถูกปล่อยออกมาโดยอัตโนมัติโดย JVM ในตอนท้าย
การใช้งานแบบคลาสสิกมีดังนี้:
ลอง {ถ้า (lock.trylock (5, timeunit.seconds)) {// ถ้ามันถูกล็อคให้ลองรอ 5s เพื่อดูว่าล็อคสามารถรับได้หรือไม่ หากไม่สามารถรับล็อคได้หลังจาก 5s ให้ส่งคืนเท็จเพื่อดำเนินการต่อ // lock.lockinctibly (); สามารถตอบสนองต่อเหตุการณ์ขัดจังหวะลอง {// การดำเนินการ} ในที่สุด {lock.unlock (); }}} catch (interruptedException e) {e.printStackTrace (); // เมื่อเธรดปัจจุบันถูกขัดจังหวะ (ขัดจังหวะ) การขัดจังหวะจะถูกโยนลง}Reentrantlock ให้ฟังก์ชั่นการควบคุมล็อคที่หลากหลายและใช้วิธีการควบคุมเหล่านี้อย่างยืดหยุ่นเพื่อปรับปรุงประสิทธิภาพของแอปพลิเคชัน อย่างไรก็ตามไม่แนะนำให้ใช้ reentrantlock ที่นี่ Reentry Lock เป็นเครื่องมือพัฒนาขั้นสูงที่มีให้ใน JDK
ReadWriteLock อ่านและเขียนล็อค
การอ่านและการเขียนการแยกเป็นแนวคิดการประมวลผลข้อมูลที่พบบ่อยมาก ควรพิจารณาเทคโนโลยีที่จำเป็นใน SQL ReadWriteLock เป็นล็อคการแยกการอ่านที่มีให้ใน JDK5 การอ่านและเขียนล็อคการแยกสามารถช่วยลดการแข่งขันล็อคเพื่อปรับปรุงประสิทธิภาพของระบบได้อย่างมีประสิทธิภาพ สถานการณ์การใช้งานสำหรับการแยกการอ่านและการเขียนส่วนใหญ่จะเป็นส่วนใหญ่ถ้าในระบบจำนวนการดำเนินการอ่านมีขนาดใหญ่กว่าการดำเนินการเขียน วิธีใช้เป็นดังนี้:
Private ReentRantReadWriteLock ReadWriteLock = ใหม่ reentRantReadWriteLock (); ล็อคส่วนตัว readlock = readWriteLock.ReadLock (); ล็อคส่วนตัว WRITELOCK = ReadWriteLock.WriteLock (); Thread.sleep (1,000); ค่าส่งคืน; } ในที่สุด {readlock.unlock (); }} วัตถุสาธารณะ handleread () พ่น InterruptedException {ลอง {writeLock.lock (); Thread.sleep (1,000); ค่าส่งคืน; } ในที่สุด {writelock.unlock (); - วัตถุเงื่อนไข
วัตถุเงื่อนไขใช้เพื่อประสานงานการทำงานร่วมกันที่ซับซ้อนระหว่างหลายเธรด ส่วนใหญ่เกี่ยวข้องกับล็อค อินสแตนซ์เงื่อนไขที่ผูกไว้กับการล็อคสามารถสร้างได้ด้วยวิธี newCondition () ในอินเตอร์เฟสล็อค ความสัมพันธ์ระหว่างวัตถุเงื่อนไขและการล็อคเป็นเหมือนการใช้สองฟังก์ชั่น object.wait (), Object.Notify () และคำหลักที่ซิงโครไนซ์
ที่นี่คุณสามารถแยกซอร์สโค้ดของ ArrayBlockingQueue:
คลาสสาธารณะ arrayblockingqueue ขยาย Abstractqueue ใช้ blockkingqueue, java.io.serializable {/** ล็อคหลักที่ปกป้องการเข้าถึงทั้งหมด*/สุดท้าย reentrantlock lock;/** เงื่อนไขสำหรับการรอ*/private final notempty;/** เงื่อนไขสำหรับการรอ orderalargumentException (); this.items = วัตถุใหม่ [ความจุ]; ล็อค = ใหม่ reentrantlock (ยุติธรรม); notempty = lock.newCondition (); // สร้างเงื่อนไข notfull = lock.newCondition ();} โมฆะสาธารณะใส่ (e e) พ่น interruptedException {checknotNull (e); สุดท้าย reentrantlock lock = this.lock; lock.lockinctibly (); ลอง {ในขณะที่ (count == items.length) notfull.await (); แทรก (e); } ในที่สุด {lock.unlock (); }} การแทรกโมฆะส่วนตัว (e x) {รายการ [putindex] = x; PutIndex = Inc (Putindex); ++ นับ; notempty.signal (); // การแจ้งเตือน} สาธารณะ e รับ () พ่น InterruptedException {สุดท้าย reentrantlock lock = this.lock; lock.lockinctibly (); ลอง {ในขณะที่ (count == 0) // ถ้าคิวว่างเปล่า notempty.await (); // จากนั้นคิวผู้บริโภคจะต้องรอสารสกัดจากสัญญาณที่ไม่ว่างเปล่า (); } ในที่สุด {lock.unlock (); }} extract ส่วนตัว e () {วัตถุสุดท้าย [] items = this.items; e x = สิ่งนี้ <e> cast (รายการ [TakeIndex]); รายการ [TakeIndex] = null; TakeIndex = Inc (TakeIndex); --นับ; notfull.signal (); // แจ้งให้ใส่ () ว่าคิวเธรดมีพื้นที่ว่างส่งคืน x;} // รหัสอื่น ๆ } Semaphore Semaphore <br /> semaphore ให้วิธีการควบคุมที่มีประสิทธิภาพยิ่งขึ้นสำหรับการทำงานร่วมกันแบบมัลติเธรด Semaphore เป็นส่วนขยายของล็อค ไม่ว่าจะเป็นตัวล็อคภายในที่ซิงโครไนซ์หรือ reentrantlock หนึ่งเธรดหนึ่งอนุญาตให้เข้าถึงทรัพยากรในเวลาเดียวกันในขณะที่สัญญาณสามารถระบุได้ว่าหลายเธรดเข้าถึงทรัพยากรในเวลาเดียวกัน จากตัวสร้างเราจะเห็น:
สัญญาณสาธารณะ (ใบอนุญาต int) {}
สัญญาณสาธารณะสาธารณะ (ใบอนุญาต int, Boolean Fair) {} // สามารถระบุได้ว่ามันยุติธรรม
ใบอนุญาตระบุสมุดเข้าถึงสำหรับเซมาฟอร์ซึ่งหมายความว่าสามารถใช้ใบอนุญาตได้กี่ใบในเวลาเดียวกัน เมื่อแต่ละเธรดใช้สำหรับใบอนุญาตทีละครั้งเท่านั้นนี่จะเทียบเท่ากับการระบุจำนวนเธรดที่สามารถเข้าถึงทรัพยากรบางอย่างในเวลาเดียวกัน นี่คือวิธีการหลักที่จะใช้:
โมฆะสาธารณะได้รับ () พ่น InterruptedException {} // พยายามขออนุญาตการเข้าถึง หากไม่สามารถใช้งานได้เธรดจะรอโดยรู้ว่าเธรดจะปล่อยสิทธิ์หรือเธรดปัจจุบันถูกขัดจังหวะ
โมฆะสาธารณะได้รับการแก้ไขอย่างต่อเนื่อง () {} // คล้ายกับการได้รับ () แต่ไม่ตอบสนองต่อการขัดจังหวะ
บูลีนสาธารณะ tryacquire () {} // พยายามรับมันจริงถ้าประสบความสำเร็จมิฉะนั้นเท็จ วิธีนี้จะไม่รอและจะกลับมาทันที
Public Boolean Tryacquire (Long Timeout, TimeUnit Unit) พ่น InterruptedException {} // ใช้เวลานานแค่ไหนในการรอ
Public Void Release () // ใช้เพื่อปล่อยสิทธิ์การใช้งานหลังจากทรัพยากรการเข้าถึงในสถานที่เสร็จสมบูรณ์เพื่อให้เธรดอื่นที่รอการอนุญาตสามารถเข้าถึงทรัพยากรได้
มาดูตัวอย่างของการใช้สัญญาณที่ให้ไว้ในเอกสาร JDK ตัวอย่างนี้อธิบายวิธีการควบคุมการเข้าถึงทรัพยากรผ่านสัญญาณ
สระว่ายน้ำระดับสาธารณะ {ส่วนตัวคงที่ int max_available = 100; semaphore สุดท้ายส่วนตัวที่มีอยู่ = semaphore ใหม่ (max_available, true); วัตถุสาธารณะ getItem () พ่น InterruptedException {arach.acquire (); // สมัครใบอนุญาต // เพียง 100 เธรดที่สามารถป้อนเพื่อรับรายการที่มีอยู่ในเวลาเดียวกัน // ถ้ามากกว่า 100 คุณต้องรอการคืนกลับ getNextavailableitem ();} โมฆะสาธารณะ putitem (วัตถุ x) {// ใส่รายการที่กำหนดกลับเข้าไปในสระว่ายน้ำ // เพิ่มรายการที่มีอยู่ปล่อยสิทธิ์การใช้งานและเธรดที่ร้องขอทรัพยากรถูกเปิดใช้งาน}} // ตัวอย่างการอ้างอิงเท่านั้นวัตถุที่ไม่ได้รับการป้องกันวัตถุ [] รายการ = วัตถุใหม่ [max_available]; // ใช้สำหรับวัตถุพูลวัตถุมัลติเพล็กซิ่งบูลีน [] ใช้ = บูลีนใหม่ [max_available]; // ฟังก์ชั่นมาร์กอัปได้รับการป้องกันวัตถุที่ซิงโครไนซ์ getNextavailableitem () {สำหรับ (int i = 0; i <max_available; ++ i) {ถ้า (! ใช้ [i]) {ใช้ [i] = true; คืนรายการ [i]; }} return null;} boolean boolean ที่ได้รับการป้องกัน markasunused (รายการวัตถุ) {สำหรับ (int i = 0; i <max_available; ++ i) {ถ้า (รายการ == รายการ [i]) {ถ้า (ใช้ [i]) {ใช้ [i] = false; กลับมาจริง; } else {return false; }}} return false;}} อินสแตนซ์นี้เพียงแค่ใช้พูลวัตถุที่มีความจุสูงสุด 100 ดังนั้นเมื่อมีคำขอวัตถุ 100 รายการในเวลาเดียวกันพูลวัตถุจะมีปัญหาการขาดแคลนทรัพยากรและเธรดที่ไม่ได้รับทรัพยากรต้องรอ เมื่อเธรดเสร็จสิ้นการใช้วัตถุจะต้องส่งคืนวัตถุไปยังพูลวัตถุ ในเวลานี้เนื่องจากทรัพยากรที่มีอยู่เพิ่มขึ้นเธรดที่รอทรัพยากรสามารถเปิดใช้งานได้
Threadlocal Thread ตัวแปรท้องถิ่น <br /> หลังจากเพิ่งเริ่มติดต่อ ThreadLocal มันเป็นเรื่องยากสำหรับฉันที่จะเข้าใจสถานการณ์การใช้งานของตัวแปรท้องถิ่นของเธรดนี้ เมื่อมองย้อนกลับไปตอนนี้ ThreadLocal เป็นวิธีแก้ปัญหาสำหรับการเข้าถึงตัวแปรระหว่างหลายเธรด ซึ่งแตกต่างจากวิธีการที่ซิงโครไนซ์และการล็อคอื่น ๆ ThreadLocal ไม่ได้ให้การล็อคเลย แต่ใช้วิธีการแลกเปลี่ยนพื้นที่สำหรับเวลาเพื่อให้แต่ละเธรดมีสำเนาของตัวแปรอิสระเพื่อความปลอดภัยของด้าย ดังนั้นจึงไม่ใช่วิธีแก้ปัญหาสำหรับการแบ่งปันข้อมูล
Threadlocal เป็นความคิดที่ดีในการแก้ปัญหาความปลอดภัยของด้าย มีแผนที่ในคลาส ThreadLocal ที่เก็บสำเนาตัวแปรสำหรับแต่ละเธรด คีย์ขององค์ประกอบในแผนที่คือวัตถุเธรดและค่าสอดคล้องกับสำเนาของตัวแปรสำหรับเธรด เนื่องจากไม่สามารถทำซ้ำค่าคีย์ได้ "วัตถุเธรด" แต่ละอันสอดคล้องกับ "สำเนาของตัวแปร" ของเธรดและถึงความปลอดภัยของเธรด
เป็นเรื่องสำคัญโดยเฉพาะอย่างยิ่ง ในแง่ของประสิทธิภาพ ThreadLocal ไม่มีประสิทธิภาพที่สมบูรณ์ เมื่อปริมาณการเกิดพร้อมกันไม่สูงมากประสิทธิภาพของการล็อคจะดีขึ้น อย่างไรก็ตามในฐานะที่เป็นชุดของโซลูชันที่ปลอดภัยจากเธรดที่ไม่เกี่ยวข้องกับล็อคอย่างสมบูรณ์โดยใช้ ThreadLocal สามารถลดการแข่งขันล็อคได้ในระดับหนึ่งในระดับสูงพร้อมกันหรือการแข่งขันที่รุนแรง
นี่คือการใช้ ThreadLocal อย่างง่าย ๆ :
Public Class TestNum {// overWrite วิธีการเริ่มต้นของ threadLocal () ผ่านคลาสภายในที่ไม่ระบุชื่อระบุค่าเริ่มต้นส่วนตัว Static Static Static Static Seqnum = new ThreadLocal () {Public Integer InitialValue () {return 0; - // รับค่าลำดับถัดไป public int int getNextNum () {seqnum.set (seqnum.get () + 1); return seqnum.get ();} โมฆะคงที่สาธารณะหลัก (สตริง [] args) {testnum sn = new testNum (); // 3 เธรดแบ่งปัน SN, แต่ละการสร้างลำดับหมายเลขทดสอบ T1 = TESTClient ใหม่ (SN); TestClient T2 = TestClient ใหม่ (SN); TestClient T3 = TestClient ใหม่ (SN); t1.start (); t2.start (); t3.start (); } การทดสอบคลาสแบบคงที่ส่วนตัวขยายเธรด {Private TestNum SN; การทดสอบสาธารณะ (testnum sn) {this.sn = sn; } โมฆะสาธารณะเรียกใช้ () {สำหรับ (int i = 0; i <3; i ++) {// แต่ละเธรดสร้าง 3 ค่าลำดับ System.out.println ("Thread [" + Thread.currentThread (). getName () + "] -> sn [" + sn.getnextnum () - ผลลัพธ์ผลลัพธ์:
เธรด [เธรด -0]> sn [1]
เธรด [เธรด -1]> sn [1]
Thread [Thread-2]> Sn [1]
เธรด [เธรด -1]> sn [2]
เธรด [เธรด -0]> sn [2]
เธรด [เธรด -1]> sn [3]
Thread [Thread-2]> Sn [2]
เธรด [เธรด -0]> sn [3]
Thread [Thread-2]> Sn [3]
ข้อมูลผลลัพธ์ผลลัพธ์สามารถพบได้ว่าแม้ว่าหมายเลขลำดับที่สร้างโดยแต่ละเธรดจะแชร์อินสแตนซ์ testnum เดียวกัน แต่พวกเขาจะไม่รบกวนกันและกัน แต่แต่ละตัวสร้างหมายเลขลำดับอิสระ นี่เป็นเพราะ ThreadLocal จัดทำสำเนาแยกต่างหากสำหรับแต่ละเธรด
ประสิทธิภาพการล็อคและการเพิ่มประสิทธิภาพของ "ล็อค" เป็นหนึ่งในวิธีการซิงโครไนซ์ที่ใช้กันมากที่สุด ในการพัฒนาปกติคุณมักจะเห็นนักเรียนหลายคนเพิ่มรหัสชิ้นใหญ่ลงในล็อคโดยตรง นักเรียนบางคนสามารถใช้วิธีล็อคหนึ่งวิธีเพื่อแก้ปัญหาการแบ่งปันทั้งหมด เห็นได้ชัดว่าการเข้ารหัสดังกล่าวไม่สามารถยอมรับได้ โดยเฉพาะอย่างยิ่งในสภาพแวดล้อมที่เกิดขึ้นพร้อมกันสูงการแข่งขันล็อคที่ดุเดือดจะนำไปสู่การลดลงของประสิทธิภาพที่ชัดเจนยิ่งขึ้นของโปรแกรม ดังนั้นการใช้อย่างสมเหตุสมผลของล็อคจึงเกี่ยวข้องโดยตรงกับประสิทธิภาพของโปรแกรม
1. เธรดค่าใช้จ่าย <br /> ในกรณีของมัลติคอร์การใช้มัลติเธรดสามารถปรับปรุงประสิทธิภาพของระบบได้อย่างมีนัยสำคัญ อย่างไรก็ตามในสถานการณ์จริงการใช้มัลติเธรดจะเพิ่มค่าใช้จ่ายของระบบเพิ่มเติม นอกเหนือจากการใช้ทรัพยากรของงานระบบเดียวกับตัวเองแล้วแอปพลิเคชันแบบหลายเธรดยังจำเป็นต้องเก็บรักษาข้อมูลที่ไม่ซ้ำกันแบบมัลติเธรดเพิ่มเติม ตัวอย่างเช่นข้อมูลเมตาของเธรดเองการตั้งเวลาเธรดการสลับบริบทของเธรด ฯลฯ
2. ลดเวลาล็อค
ในโปรแกรมที่ใช้ล็อคสำหรับการควบคุมพร้อมกันเมื่อล็อคแข่งขันเวลาถือล็อคของเธรดเดียวมีความสัมพันธ์โดยตรงกับประสิทธิภาพของระบบ หากเธรดถือล็อคเป็นเวลานานการแข่งขันสำหรับการล็อคจะรุนแรงขึ้น ดังนั้นในระหว่างกระบวนการพัฒนาโปรแกรมเวลาในการครอบครองล็อคบางอย่างควรย่อให้เล็กสุดเพื่อลดความเป็นไปได้ของการยกเว้นซึ่งกันและกันระหว่างเธรด ตัวอย่างเช่นรหัสต่อไปนี้:
public synchronized void syncmehod () {beforemethod (); mutexmethod (); aftermethod ();} หากมีเพียงวิธี mutexmethod () ในอินสแตนซ์นี้คือการซิงโครนัส แต่ใน beforemethod () และ aftermethod () ไม่จำเป็นต้องมีการควบคุมการซิงโครไนซ์ หาก beforeMethod () และ amftermethod () เป็นวิธีการหนา ๆ มันจะใช้เวลานานสำหรับ CPU ในเวลานี้หากการเกิดพร้อมกันมีขนาดใหญ่การใช้รูปแบบการซิงโครไนซ์นี้จะนำไปสู่การเพิ่มขึ้นของเธรดที่เพิ่มขึ้นอย่างมาก เนื่องจากเธรดการดำเนินการในปัจจุบันจะปล่อยล็อคหลังจากดำเนินการทั้งหมดแล้ว
ต่อไปนี้เป็นวิธีแก้ปัญหาที่ได้รับการปรับปรุงซึ่งจะซิงโครไนซ์เมื่อจำเป็นเท่านั้นเพื่อให้เวลาสำหรับเธรดที่จะถือล็อคสามารถลดลงได้อย่างมีนัยสำคัญและปริมาณงานของระบบสามารถปรับปรุงได้ รหัสมีดังนี้:
โมฆะสาธารณะ syncMehod () {beforemethod (); ซิงโครไนซ์ (นี่) {mutexmethod ();} aftermethod ();} 3. ลดขนาดอนุภาคล็อค
การลดความละเอียดล็อคเป็นวิธีที่มีประสิทธิภาพในการลดการแข่งขันสำหรับล็อคแบบมัลติเธรด สถานการณ์การใช้งานทั่วไปของเทคโนโลยีนี้คือคลาสที่เกิดขึ้นพร้อมกัน ใน HashMap ธรรมดาเมื่อใดก็ตามที่การดำเนินการ ADD () หรือรับ () ดำเนินการในคอลเลกชันล็อคของวัตถุคอลเลกชันจะได้รับเสมอ การดำเนินการนี้เป็นพฤติกรรมแบบซิงโครนัสอย่างสมบูรณ์เนื่องจากการล็อคอยู่ในวัตถุคอลเลกชันทั้งหมด ดังนั้นในการพร้อมกันที่สูงการแข่งขันล็อคที่รุนแรงจะส่งผลกระทบต่อปริมาณงานของระบบ
หากคุณได้อ่านซอร์สโค้ดคุณควรรู้ว่า HASHMAP ถูกนำไปใช้ในรายการอาร์เรย์ + ที่เชื่อมโยง ConcurrentHashMap แบ่ง HASHMAP ทั้งหมดออกเป็นหลายส่วน (เซ็กเมนต์) และแต่ละเซ็กเมนต์เป็น sub-hashmap หากคุณต้องการเพิ่มรายการตารางใหม่คุณจะไม่ล็อค HashMap สายการค้นหายี่สิบจะได้รับส่วนที่ควรจัดเก็บตารางตาม HashCode จากนั้นล็อคส่วนและดำเนินการดำเนินการ Put () ให้เสร็จสิ้น ด้วยวิธีนี้ในสภาพแวดล้อมแบบมัลติเธรดหากหลายเธรดดำเนินการเขียนในเวลาเดียวกันตราบใดที่รายการที่เขียนไม่ได้มีอยู่ในเซ็กเมนต์เดียวกันการขนานที่แท้จริงสามารถทำได้ระหว่างเธรด สำหรับการใช้งานที่เฉพาะเจาะจงฉันหวังว่าผู้อ่านจะใช้เวลาในการอ่านซอร์สโค้ดของคลาส ConcurrentHashMap ดังนั้นฉันจะไม่อธิบายมันมากเกินไปที่นี่
4. ล็อคการแยก <br /> A readwriteLock อ่านและเขียนล็อคที่กล่าวถึงก่อนหน้านี้จากนั้นส่วนขยายของการอ่านและการเขียนการเขียนคือการแยกล็อค ซอร์สโค้ดของการแยกล็อคสามารถพบได้ใน JDK
Public Class LinkedBlockingQueue ขยาย Abstractqueue ใช้ blockingqueue, java.io.serializable {/*ล็อคถือโดยการสำรวจ, การสำรวจ, ฯลฯ/ส่วนตัวสุดท้าย reentrantlock takelock = ใหม่ reentrantlock ();/** รอคิว reentrantlock putlock = ใหม่ reentrantlock ();/** รอคิวรอสำหรับการรอ*/เงื่อนไขสุดท้ายส่วนตัว notfull = putlock.newcondition (); Public E Take () พ่น InterruptedException {ex; int c = -1; Atomicinteger สุดท้ายนับ = this.count; สุดท้าย reentrantlock takelock = this.takelock; takelock.lockintibly (); // ไม่สามารถมีสองเธรดในการอ่านข้อมูลในเวลาเดียวกันลอง {ในขณะที่ (count.get () == 0) {// หากไม่มีข้อมูลให้รอการแจ้งเตือนของ Put () notempty.await (); } x = dequeue (); // ลบรายการ c = count.getanddecrement (); // ขนาดลบ 1 ถ้า (c> 1) notempty.signal (); // แจ้งการดำเนินการอื่น () การดำเนินการ} ในที่สุด {takelock.unlock (); // ปล่อยล็อค} ถ้า (C == ความจุ) signalNotFull (); // แจ้งเตือนการดำเนินการ () มีการส่งคืนอวกาศฟรี x;} โมฆะสาธารณะใส่ (e e) พ่น InterruptedException {ถ้า (e == null) โยน nullpointerexception ใหม่ (); // หมายเหตุ: อนุสัญญาในการพัตต์/ใช้/ฯลฯ ทั้งหมดคือการตั้งค่า var // จำนวนการถือครองลบเพื่อระบุความล้มเหลวเว้นแต่ตั้งค่า int c = -1; โหนด <E> node = new node (e); สุดท้าย reentrantlock putlock = this.putlock; Atomicinteger สุดท้ายนับ = this.count; putlock.lockinctibly (); // ไม่สามารถมีสองเธรดที่ใส่ข้อมูลในเวลาเดียวกันลอง { / * * โปรดทราบว่าการนับนั้นใช้ในตัวป้องกันการรอแม้ว่าจะไม่ได้รับการป้องกันโดยการล็อค สิ่งนี้ใช้งานได้เนื่องจากการนับสามารถลดลงได้ ณ จุดนี้เท่านั้น (วางอื่น ๆ ทั้งหมดถูกปิด * ออกโดยล็อค) และเรา (หรือการรอคอยอื่น ๆ ) จะถูกลงนามหากมันเปลี่ยนจากความจุ ในทำนองเดียวกัน * สำหรับการใช้งานอื่น ๆ ทั้งหมดในยามรออื่น ๆ */ ในขณะที่ (count.get () == ความจุ) {// ถ้าคิวเต็มให้รอ notfull.await (); } enqueue (โหนด); // เข้าร่วมคิว c = count.getandincrement (); // ขนาดบวก 1 ถ้า (c + 1 <ความจุ) notfull.signal (); // แจ้งเธรดอื่น ๆ หากมีพื้นที่เพียงพอ} ในที่สุด {putlock.unlock (); // ปล่อยล็อค} ถ้า (c == 0) signalNetEmpty (); // หลังจากการแทรกสำเร็จสิ่งที่ต้องอธิบายที่นี่คือฟังก์ชั่น () และ put () เป็นอิสระจากกันและไม่มีความสัมพันธ์ระหว่างการแข่งขันล็อคระหว่างพวกเขา คุณจะต้องแข่งขันเพื่อ Takelock และ Putlock ภายในวิธีการที่เกี่ยวข้องกับการใช้ () และ put () ดังนั้นความเป็นไปได้ของการแข่งขันล็อคจะอ่อนแอลง
5. ล็อคความหยาบ <r /> การลดเวลาล็อคดังกล่าวข้างต้นและความละเอียดดังกล่าวเพื่อให้ได้เวลาที่สั้นที่สุดสำหรับแต่ละเธรดเพื่อยึดล็อค อย่างไรก็ตามควรมีการจับระดับระดับด้วยความละเอียด หากมีการร้องขอล็อคอย่างต่อเนื่องให้ซิงโครไนซ์และปล่อยออกมามันจะใช้ทรัพยากรที่มีค่าของระบบและเพิ่มค่าใช้จ่ายของระบบ
สิ่งที่เราต้องรู้คือเมื่อเครื่องเสมือนพบชุดของคำขออย่างต่อเนื่องและการปล่อยล็อคเดียวกันมันจะรวมการดำเนินการล็อคทั้งหมดลงในคำขอเดียวกับการล็อคซึ่งจะช่วยลดจำนวนคำขอสำหรับการล็อค การดำเนินการนี้เรียกว่าล็อคหยาบ นี่คือการสาธิตตัวอย่างการรวม:
โมฆะสาธารณะ syncMehod () {ซิงโครไนซ์ (ล็อค) {method1 ();} ซิงโครไนซ์ (ล็อค) {method2 ();}} รูปแบบหลังจากการรวม JVM: โมฆะสาธารณะ syncMehod () {ซิงโครไนซ์ (ล็อค) {วิธีการ 1 (); method2 ();ดังนั้นการบูรณาการดังกล่าวทำให้นักพัฒนาของเรามีผลการสาธิตที่ดีต่อความเข้าใจของความละเอียดล็อค
การประมวลผลแบบขนานแบบล็อคขนาน <br /> ข้างต้นใช้เวลาส่วนใหญ่ในการพูดคุยเกี่ยวกับการล็อคและยังมีการกล่าวถึงว่าการล็อคจะนำค่าใช้จ่ายทรัพยากรเพิ่มเติมสำหรับการสลับบริบทบางอย่าง ในการพร้อมกันที่สูงการแข่งขันที่รุนแรงสำหรับ "ล็อค" อาจกลายเป็นคอขวดของระบบ ดังนั้นจึงสามารถใช้วิธีการซิงโครไนซ์ที่ไม่ปิดกั้นได้ที่นี่ วิธีที่ปราศจากล็อคนี้ยังคงสามารถมั่นใจได้ว่าข้อมูลและโปรแกรมรักษาความสอดคล้องระหว่างหลายเธรดในสภาพแวดล้อมที่มีการพร้อมกันสูง
1. การซิงโครไนซ์ที่ไม่ปิดกั้น/การล็อค
วิธีการซิงโครไนซ์ที่ไม่ปิดกั้นนั้นสะท้อนให้เห็นใน ThreadLocal ก่อนหน้า แต่ละเธรดมีสำเนาตัวแปรอิสระของตัวเองดังนั้นจึงไม่จำเป็นต้องรอซึ่งกันและกันเมื่อคำนวณแบบขนาน ที่นี่ผู้เขียนส่วนใหญ่แนะนำวิธีการควบคุมพร้อมกันที่ไม่มีการล็อคที่สำคัญกว่าโดยใช้อัลกอริทึมการเปรียบเทียบและสลับอัลกอริทึม CAS
กระบวนการของอัลกอริทึม CAS: มี 3 พารามิเตอร์ CAS (V, E, N) V แสดงถึงตัวแปรที่จะอัปเดต E แสดงถึงค่าที่คาดหวังและ n หมายถึงค่าใหม่ ค่าของ V จะถูกตั้งค่าเป็น N เฉพาะเมื่อค่า V เท่ากับค่า E หากค่า V แตกต่างจากค่า E นั่นหมายความว่าเธรดอื่น ๆ ได้ทำการอัปเดตและเธรดปัจจุบันจะไม่ทำอะไรเลย ในที่สุด CAS จะส่งคืนค่าที่แท้จริงของ V. ปัจจุบันเมื่อใช้งาน CAS มันจะดำเนินการด้วยทัศนคติในแง่ดีและเชื่อเสมอว่ามันสามารถดำเนินการเสร็จสมบูรณ์ได้ เมื่อหลายเธรดใช้ CAS เพื่อใช้งานตัวแปรในเวลาเดียวกันมีเพียงหนึ่งเดียวเท่านั้นที่จะชนะและได้รับการปรับปรุงให้สำเร็จในขณะที่ส่วนที่เหลือของ Junhui ล้มเหลว เธรดที่ล้มเหลวจะไม่ถูกระงับจะได้รับการบอกว่าความล้มเหลวได้รับอนุญาตและได้รับอนุญาตให้ลองอีกครั้งและแน่นอนว่าเธรดที่ล้มเหลวจะอนุญาตให้มีการยกเลิกการดำเนินการ ขึ้นอยู่กับหลักการนี้การดำเนินการ CAS นั้นทันเวลาโดยไม่มีการล็อคและเธรดอื่น ๆ ยังสามารถตรวจจับสัญญาณรบกวนไปยังเธรดปัจจุบันและจัดการได้อย่างเหมาะสม
2. การทำงานของน้ำหนักอะตอม
java.util.concurrent.atomic แพคเกจของ JDK ให้คลาสการทำงานของอะตอมที่ใช้งานโดยใช้อัลกอริทึมที่ปราศจากล็อคและรหัสส่วนใหญ่ใช้การใช้งานรหัสดั้งเดิม นักเรียนที่สนใจสามารถติดตามรหัสระดับดั้งเดิมต่อไปได้ ฉันจะไม่โพสต์การใช้งานรหัสพื้นผิวที่นี่
ส่วนใหญ่ใช้ตัวอย่างเพื่อแสดงช่องว่างประสิทธิภาพระหว่างวิธีการซิงโครไนซ์ทั่วไปและการซิงโครไนซ์ฟรีล็อค:
Public Class Public Testatomic {ส่วนตัวคงที่ int max_threads = 3; int int int สุดท้ายของส่วนตัว {count = 3; int int สุดท้ายของเอกชน int target_count = 100 * 10,000; บัญชี Atomicinteger ส่วนตัว = Atomicinteger ใหม่ (0); เรียกใช้ {ชื่อสตริง; เวลาเริ่มต้นนาน Testatomic Out; SyncThread สาธารณะ (Testatomic O, Long StartTime) {this.out = o; this.starttime = starttime; } @Override โมฆะสาธารณะเรียกใช้ () {int v = out.inc (); ในขณะที่ (v <target_count) {v = out.inc (); } endtime long = system.currentTimeMillis (); System.out.println ("SyncThread ใช้จ่าย:" + (endtime - starttime) + "ms" + ", v =" + v); }} คลาสสาธารณะ AtomicThread ใช้งาน Runnable {ชื่อสตริง; เวลาเริ่มต้นนาน Public AtomicThread (Long Starttime) {this.starttime = starttime; } @Override โมฆะสาธารณะ Run () {int v = account.incrementandget (); ในขณะที่ (v <target_count) {v = account.incrementandget (); } endtime long = system.currentTimeMillis (); System.out.println ("AtomicThread ใช้จ่าย:" + (endtime - starttime) + "ms" + ", v =" + v); }}@testPublic เป็นโมฆะ testsync () พ่น InterruptedException {ExecutorService exe = executors.newFixedThreadPool (max_threads); Long StartTime = System.currentTimeMillis (); SyncThread sync = new syncThread (นี่, starttime); สำหรับ (int i = 0; i <task_count; i ++) {exe.submit (ซิงค์); } thread.sleep (10,000);}@testpublic void testatomic () พ่น InterruptedException {ExecutorService exe = executors.newFixedThreadPool (max_threads); Long StartTime = System.currentTimeMillis (); AtomicThread Atomic = ใหม่ AtomicThread (StartTime); สำหรับ (int i = 0; i <task_count; i ++) {exe.submit (อะตอม); } thread.sleep (10,000);}} ผลการทดสอบมีดังนี้:
testsync ():
SyncThread ใช้จ่าย: 201ms, v = 1000002
SyncThread ใช้จ่าย: 201ms, v = 10,00000
SyncThread ใช้จ่าย: 201ms, v = 1000001
testatomic ():
AtomicThread ใช้จ่าย: 43ms, v = 10,00000
AtomicThread ใช้จ่าย: 44ms, V = 1000001
AtomicThread ใช้จ่าย: 46ms, V = 1000002
ฉันเชื่อว่าผลการทดสอบดังกล่าวจะสะท้อนถึงความแตกต่างของประสิทธิภาพระหว่างการล็อคภายในและอัลกอริทึมการซิงโครไนซ์ที่ไม่ปิดกั้น ดังนั้นผู้เขียนแนะนำให้พิจารณาชั้นเรียนอะตอมนี้โดยตรงภายใต้อะตอม
บทสรุป
ในที่สุดฉันได้แยกแยะสิ่งที่ฉันต้องการแสดง ในความเป็นจริงยังมีบางคลาสเช่น Countdownlatch ที่ยังไม่ได้กล่าวถึง อย่างไรก็ตามสิ่งที่กล่าวถึงข้างต้นเป็นแกนหลักของการเขียนโปรแกรมพร้อมกันอย่างแน่นอน บางทีผู้อ่านบางคนสามารถเห็นจุดความรู้มากมายบนอินเทอร์เน็ต แต่ฉันก็ยังคิดว่าการเปรียบเทียบความรู้สามารถพบได้ในสถานการณ์การใช้งานที่เหมาะสม ดังนั้นนี่คือเหตุผลที่บรรณาธิการได้รวบรวมบทความนี้และฉันหวังว่าบทความนี้จะช่วยนักเรียนได้มากขึ้น
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น