Java5 มีการอ่านและเขียนล็อคในแพ็คเกจ java.util.concurrent แล้ว อย่างไรก็ตามเราควรเข้าใจหลักการที่อยู่เบื้องหลังการดำเนินการ
การใช้งาน Java ของการอ่าน/เขียนล็อค
ก่อนอื่นให้ให้ภาพรวมของเงื่อนไขสำหรับการอ่านและการเขียนการเข้าถึงทรัพยากร:
การอ่านไม่มีเธรดกำลังดำเนินการเขียนและไม่มีเธรดใดที่ร้องขอการดำเนินการเขียน
ไม่มีเธรดที่กำลังดำเนินการอ่านและเขียน
หากเธรดต้องการอ่านทรัพยากรตราบใดที่ไม่มีเธรดกำลังเขียนลงในทรัพยากรและไม่มีคำขอเธรดที่เขียนไปยังทรัพยากร เราคิดว่าการร้องขอการดำเนินการเขียนมีความสำคัญมากกว่าคำขอสำหรับการดำเนินการอ่านดังนั้นเราต้องเพิ่มลำดับความสำคัญของคำขอเขียน นอกจากนี้หากการดำเนินการอ่านเกิดขึ้นบ่อยครั้งและเราจะไม่เพิ่มลำดับความสำคัญของการดำเนินการเขียนแล้ว "ความหิว" จะเกิดขึ้น เธรดที่ร้องขอการดำเนินการเขียนจะถูกบล็อกจนกว่าเธรดการอ่านทั้งหมดจะถูกปลดล็อคจาก ReadWriteLock หากใบอนุญาตการดำเนินการอ่านของเธรดใหม่รับประกันได้เสมอเธรดที่รอการดำเนินการเขียนจะยังคงบล็อกต่อไปและผลลัพธ์จะเป็น "ความหิว" ดังนั้นการดำเนินการอ่านสามารถรับประกันได้ว่าจะดำเนินการต่อเมื่อไม่มีเธรดที่ล็อค ReadWriteLock สำหรับการดำเนินการเขียนและไม่มีการร้องขอเธรดการล็อคให้พร้อมสำหรับการดำเนินการเขียน
เมื่อเธรดอื่นไม่อ่านหรือเขียนการดำเนินการบนทรัพยากรที่ใช้ร่วมกันเธรดอาจได้รับการล็อคการเขียนสำหรับทรัพยากรที่ใช้ร่วมกันจากนั้นเขียนการดำเนินการบนทรัพยากรที่ใช้ร่วมกัน ไม่สำคัญว่าจะมีกี่เธรดที่ร้องขอล็อคเขียนและตามลำดับใดเว้นแต่คุณต้องการให้แน่ใจว่ามีความเป็นธรรมของคำขอล็อคการเขียน
ตามคำอธิบายข้างต้นการล็อคการอ่าน/เขียนจะถูกนำไปใช้อย่างง่าย ๆ และรหัสมีดังนี้
Public Class ReadWriteLock {ตัวอ่าน INT ส่วนตัว = 0; นักเขียน int ส่วนตัว = 0; private int writeRequests = 0; public synchronized void lockread () พ่น InterruptedException {ในขณะที่ (นักเขียน> 0 || writeRequests> 0) {wait (); } ผู้อ่าน ++; } โมฆะที่ซิงโครไนซ์สาธารณะปลดล็อค () {ผู้อ่าน-; แจ้งเตือน (); } public synchronized void lockwrite () พ่น InterruptedException {writeRequests ++; ในขณะที่ (ผู้อ่าน> 0 || นักเขียน> 0) {รอ (); } writeRequests-; นักเขียน ++; } public synchronized void unlockWrite () พ่น InterruptedException {นักเขียน-; แจ้งเตือน (); -ในคลาส ReadWriteLock อ่านล็อคและเขียนล็อคแต่ละอันมีวิธีการรับและปล่อยล็อค
การใช้งานการอ่านล็อคอยู่ใน Lockread () ตราบใดที่ไม่มีเธรดที่มีล็อคเขียน (นักเขียน == 0) และไม่มีเธรดที่ขอล็อคการเขียน (writeRequests == 0) เธรดทั้งหมดที่ต้องการรับล็อคอ่านสามารถได้รับได้สำเร็จ
การใช้งานการเขียนล็อคอยู่ใน LockWrite () เมื่อเธรดต้องการรับการล็อคการเขียนมันจะเพิ่ม 1 ลงในหมายเลขคำขอล็อคการเขียน (writeRequests ++) จากนั้นตรวจสอบว่าสามารถรับล็อคการเขียนได้หรือไม่ เมื่อไม่มีเธรดถือล็อคอ่าน (ตัวอ่าน == 0) และไม่มีเธรดถือล็อคเขียน (นักเขียน == 0) คุณสามารถรับล็อคเขียนได้ ไม่สำคัญว่ามีกี่เธรดที่ร้องขอให้เขียนล็อค
ควรสังเกตว่าทั้งในการปลดล็อคการปลดล็อคการปลดล็อควิธีการแจ้งเตือนจะเรียกว่าแทนการแจ้งเตือน เพื่ออธิบายเหตุผลนี้เราสามารถจินตนาการถึงสถานการณ์ต่อไปนี้:
หากเธรดกำลังรอที่จะได้รับการล็อคการอ่านและเธรดกำลังรอที่จะได้รับการล็อคการเขียน หากหนึ่งในเธรดที่รอการล็อคอ่านถูกปลุกด้วยวิธีการแจ้งเตือน แต่เนื่องจากยังมีเธรดที่ขอล็อคเขียน (writeRequests> 0) เธรดที่ถูกปลุกจะเข้าสู่สถานะการบล็อกอีกครั้ง อย่างไรก็ตามไม่มีเธรดใดที่รอการล็อคการเขียนถูกปลุกให้ตื่นขึ้นราวกับว่าไม่มีอะไรเกิดขึ้น (หมายเหตุของนักแปล: การสูญเสียสัญญาณ) หากคุณใช้วิธีการ NotifyAll เธรดทั้งหมดจะถูกปลุกและพิจารณาว่าพวกเขาสามารถรับล็อคที่พวกเขาร้องขอได้หรือไม่
นอกจากนี้ยังมีประโยชน์อย่างหนึ่งในการใช้ NotifyAll หากเธรดการอ่านหลายรายการกำลังรอการล็อคอ่านและไม่มีเธรดกำลังรอการล็อคการเขียนหลังจากโทรหา UnlockWrite () เธรดทั้งหมดที่รอการล็อคการอ่านสามารถรับการล็อคอ่านได้ทันทีแทนที่จะเป็นเพียงครั้งเดียว
เข้าสู่การอ่าน/เขียนอีกครั้ง
การอ่าน/ล็อคการเขียนที่นำมาใช้ด้านบนไม่ได้กลับเข้ามาใหม่และจะถูกบล็อกเมื่อเธรดที่ถือคำขอล็อคการเขียนไว้แล้วการล็อคการเขียนอีกครั้ง เหตุผลก็คือมีเธรดการเขียนอยู่แล้ว - มันเป็นของตัวเอง ลองพิจารณาตัวอย่างต่อไปนี้:
เพื่อที่จะทำให้ ReadWriteLock กลับมาได้ใหม่จำเป็นต้องมีการปรับปรุงบางอย่าง ต่อไปนี้จะจัดการกับการเข้าสู่การอ่านอีกครั้งและการเข้าล็อคการเขียนอีกครั้งตามลำดับ
อ่าน Lock Reenter
เพื่อที่จะทำให้การอ่านล็อคของ ReadWriteLock reentrant เราต้องสร้างกฎสำหรับการอ่าน Reentrant ก่อน:
เพื่อให้แน่ใจว่าล็อคการอ่านในเธรดจะกลับเข้ามาใหม่ไม่ว่าจะเป็นไปตามเงื่อนไขสำหรับการได้รับการล็อคอ่าน (ไม่มีคำขอเขียนหรือเขียน) หรือถือล็อคอ่านอยู่แล้ว (ไม่ว่าจะมีคำขอเขียนหรือไม่ก็ตาม) ในการตรวจสอบว่าเธรดได้ทำการล็อคการอ่านแล้วแผนที่สามารถใช้เก็บเธรดที่มีการล็อคการอ่านแล้วและจำนวนครั้งที่เธรดที่เกี่ยวข้องได้รับการล็อคการอ่าน เมื่อมีความจำเป็นต้องพิจารณาว่าเธรดสามารถรับการล็อคการอ่านข้อมูลที่เก็บไว้ในแผนที่จะใช้ในการตัดสินหรือไม่ ต่อไปนี้เป็นรหัสที่แก้ไขของวิธีการล็อคและปลดล็อคการอ่าน:
คลาสสาธารณะ readWriteLock {แผนที่ส่วนตัว <เธรด, จำนวนเต็ม> readingThreads = ใหม่ hashmap <เธรด, จำนวนเต็ม> (); นักเขียน int ส่วนตัว = 0; private int writeRequests = 0; public synchronized void lockread () พ่น interruptedException {เธรด callyThread = thread.currentThread (); ในขณะที่ (! ManGranTreadAccess (CallyThread)) {รอ (); } readingThreads.put (callyThread, (getAccessCount (callyThread) + 1)); } public synchronized void unlockread () {เธรด callyThread = thread.currentThread (); intactCount = getAccessCount (callyThread); if (accessCount == 1) {readingThreads.remove (callyThread); } else {readingThreads.put (callyThread, (AccessCount -1)); } แจ้งเตือน (); } บูลีนส่วนตัว CanGranTreadAccess (เธรด callyThread) {ถ้า (นักเขียน> 0) ส่งคืน false; ถ้า (isReader (callyThread) return true; if (writeRequests> 0) return false; return true;} int private int getReadAccessCount (เธรด callyThread) {integer accessCount = readingThreads.get (callingThread); if (accessCount == null) กลับมา ReadingThreads.get (CallingThread)! = null;}}ในรหัสเราจะเห็นได้ว่าการเข้าล็อคการอ่านใหม่นั้นอนุญาตให้ใช้เฉพาะในกรณีที่ไม่มีเธรดที่มีล็อคการเขียน นอกจากนี้ล็อคการอ่านซ้ำอีกครั้งมีลำดับความสำคัญสูงกว่าการเขียนล็อค
เขียนล็อคอีกครั้ง
เขียนล็อคใหม่ได้รับอนุญาตเฉพาะเมื่อเธรดถือล็อคการเขียนอยู่แล้ว (กลับมาล็อคเขียน) ต่อไปนี้เป็นรหัสที่แก้ไขของวิธีการล็อคและปลดล็อค
คลาสสาธารณะ readWriteLock {แผนที่ส่วนตัว <เธรด, จำนวนเต็ม> readingThreads = ใหม่ hashmap <เธรด, จำนวนเต็ม> (); int int private writeaccesses = 0; private int writeRequests = 0; การเขียนเธรดส่วนตัว = null; Void Lockwrite ที่ซิงโครไนซ์สาธารณะ () พ่น InterruptedException {WriteRequests ++; เธรด callyThread = thread.currentthread (); ในขณะที่ (! } writeRequests-; writeaccesses ++; WritingThread = CallingThread; } public synchronized void unlockWrite () พ่น interruptedException {writeaccesses-; if (writeaccesses == 0) {writingThread = null; } แจ้งเตือน (); } บูลีนส่วนตัว CanGrantWriteAccess (เธรด callyThread) {ถ้า (hasreaders ()) ส่งคืน false; if (writingThread == null) return true; if (! iSwriter (callingThread)) ส่งคืน false; กลับมาจริง; } บูลีนส่วนตัว hasreaders () {return readingThreads.size ()> 0; } บูลีนส่วนตัว iSwriter (เธรด callyThread) {return writingThread == callingThread; -ให้ความสนใจกับวิธีการจัดการกับมันเมื่อพิจารณาว่าเธรดปัจจุบันสามารถรับล็อคการเขียนได้หรือไม่
อ่านการอัพเกรดล็อคเพื่อเขียนล็อค
บางครั้งเราต้องการเธรดที่มีล็อคอ่านเพื่อรับล็อคการเขียน ในการอนุญาตให้ดำเนินการดังกล่าวเธรดนี้จะต้องเป็นเธรดเดียวที่มีล็อคอ่าน WriteLock () ต้องทำการเปลี่ยนแปลงบางอย่างเพื่อให้บรรลุเป้าหมายนี้:
คลาสสาธารณะ readWriteLock {แผนที่ส่วนตัว <เธรด, จำนวนเต็ม> readingThreads = ใหม่ hashmap <เธรด, จำนวนเต็ม> (); int int private writeaccesses = 0; private int writeRequests = 0; การเขียนเธรดส่วนตัว = null; Void Lockwrite ที่ซิงโครไนซ์สาธารณะ () พ่น InterruptedException {WriteRequests ++; เธรด callyThread = thread.currentthread (); ในขณะที่ (! } writeRequests-; writeaccesses ++; WritingThread = CallingThread; } public synchronized void unlockWrite () พ่น interruptedException {writeaccesses-; if (writeaccesses == 0) {writingThread = null; } แจ้งเตือน (); } บูลีนส่วนตัว CanGrantWriteAccess (เธรด callyThread) {ถ้า (isonlyReader (callyThread)) ส่งคืนจริง; if (hasreaders ()) ส่งคืน false; if (writingThread == null) return true; if (! iSwriter (callingThread)) ส่งคืน false; กลับมาจริง; } บูลีนส่วนตัว hasreaders () {return readingThreads.size ()> 0; } บูลีนส่วนตัว iSwriter (เธรด callyThread) {return writingThread == callingThread; } บูลีนส่วนตัว isonlyReader (เธรดเธรด) {return readers == 1 && ReadingThreads.get (callyThread)! = null; -ตอนนี้คลาส ReadWriteLock สามารถอัพเกรดได้จากล็อคอ่านเป็นล็อคการเขียน
เขียนล็อคลงเพื่ออ่านล็อค
บางครั้งเธรดที่มีล็อคเขียนก็ต้องการอ่านล็อค หากเธรดมีล็อคการเขียนแล้วเธรดอื่น ๆ โดยธรรมชาติจะไม่สามารถอ่านล็อคหรือล็อคเขียนได้ ดังนั้นจึงไม่มีอันตรายสำหรับเธรดที่มีล็อคการเขียนแล้วรับล็อคอ่าน เราเพียงแค่ต้องทำการปรับเปลี่ยนอย่างง่ายสำหรับวิธีการ CanGranTreadAccess ด้านบน:
Public Class ReadWriteLock {Private Boolean CanGranTreadAccess (เธรด callyThread) {if (iSwriter (callyThread)) ส่งคืนจริง; if (writingThread! = null) ส่งคืน false; if (iSreader (callyThread) return true; if (writeRequests> 0) return false; return true;}}การใช้งาน ReadWriteLock ที่สมบูรณ์
ด้านล่างคือการใช้งาน ReadWriteLock ที่สมบูรณ์ เพื่ออำนวยความสะดวกในการอ่านและทำความเข้าใจเกี่ยวกับรหัสรหัสข้างต้นได้รับการปรับปรุงใหม่ รหัส Refactored มีดังนี้
คลาสสาธารณะ readWriteLock {แผนที่ส่วนตัว <เธรด, จำนวนเต็ม> readingThreads = ใหม่ hashmap <เธรด, จำนวนเต็ม> (); int int private writeaccesses = 0; private int writeRequests = 0; การเขียนเธรดส่วนตัว = null; public synchronized void lockread () พ่น interruptedException {เธรด callyThread = thread.currentThread (); ในขณะที่ (! ManGranTreadAccess (CallyThread)) {รอ (); } readingThreads.put (callyThread, (getReadAccessCount (callyThread) + 1)); } บูลีนส่วนตัว CanGranTreadAccess (เธรด callyThread) {ถ้า (iSwriter (callyThread)) ส่งคืนจริง; ถ้า (haswriter ()) ส่งคืนเท็จ; ถ้า (isReader (callingThread)) ส่งคืนจริง; ถ้า (haswriteRequests ()) ส่งคืนเท็จ; กลับมาจริง; } public synchronized void unlockread () {เธรด callyThread = thread.currentThread (); if (! isReader (callyThread)) {โยน unlandalMonitorStateException ใหม่ ("เธรดการโทรไม่ได้" + "กดล็อคอ่านบน readWriteLock นี้"); } intaccessCount = getReadAccessCount (callyThread); if (accessCount == 1) {readingThreads.remove (callyThread); } else {readingThreads.put (callyThread, (AccessCount -1)); } แจ้งเตือน (); } public synchronized void lockwrite () พ่น InterruptedException {writeRequests ++; เธรด callyThread = thread.currentthread (); ในขณะที่ (! } writeRequests-; writeaccesses ++; WritingThread = CallingThread; } public synchronized void unlockWrite () พ่น InterruptedException {ถ้า (! iswriter (thread.currentthread ()) {โยน lexlormmonitonstateException ใหม่ ("การเรียกเธรดไม่ได้" + "ล็อคการเขียนบน {readwriteLock"); Boolean CangrantWriteAccess (เธรด callyThread) {ถ้า isonlyReader (callyThread)) return true; == null) return 0; ReadingThreads.get (CallingThread)! = null; } Boolean Haswriter ส่วนตัว () {return writingThread! = null; } บูลีนส่วนตัว iSwriter (เธรด callyThread) {return writingThread == callingThread; } บูลีนส่วนตัว haswriteRequests () {return this.writeRequests> 0; -โทรปลดล็อค () ในที่สุด
เมื่อใช้ ReadWriteLock เพื่อปกป้องโซนที่สำคัญหากโซนวิกฤตอาจมีข้อยกเว้นเป็นสิ่งสำคัญที่จะต้องเรียก ReadunLock () และ WriteUnLock () ในบล็อกสุดท้าย สิ่งนี้ทำเพื่อให้แน่ใจว่าสามารถปลดล็อค ReadWriteLock ได้สำเร็จและเธรดอื่น ๆ สามารถขอล็อคได้ นี่คือตัวอย่าง:
lock.lockWrite (); ลอง {// ทำรหัสส่วนที่สำคัญซึ่งอาจโยนข้อยกเว้น} ในที่สุด {lock.unlockWrite ();}โครงสร้างรหัสข้างต้นสามารถมั่นใจได้ว่า ReadWriteLock จะได้รับการปล่อยตัวเมื่อมีการโยนข้อยกเว้นในพื้นที่วิกฤต หากวิธีการปลดล็อคการเขียนไม่ได้ถูกเรียกในบล็อกสุดท้ายเมื่อมีการโยนข้อยกเว้นในส่วนวิกฤต ReadWriteLock จะยังคงอยู่ในสถานะล็อคการเขียนซึ่งจะทำให้เธรดการเรียกล็อคดร็อค () หรือ lockwrite () เพื่อบล็อก ปัจจัยเดียวที่สามารถกลับมาอีกครั้ง readwriteLock อาจเป็นได้ว่า ReadWriteLock เป็นอีกครั้ง เมื่อมีการโยนข้อยกเว้นเธรดสามารถรับล็อคได้สำเร็จจากนั้นเรียกใช้ส่วนที่สำคัญและเรียกใช้ LockWrite () อีกครั้งซึ่งจะปล่อย ReadWriteLock อีกครั้ง แต่ถ้าเธรดไม่ได้รับการล็อคอีกต่อไป ดังนั้นการเรียก UnlockWrite ในที่สุดจึงสำคัญมากสำหรับการเขียนรหัสที่แข็งแกร่ง
ข้างต้นคือการรวบรวมข้อมูลมัลติเธรด Java เราจะยังคงเพิ่มข้อมูลที่เกี่ยวข้องในอนาคต ขอบคุณสำหรับการสนับสนุนเว็บไซต์นี้!