Semaphore เป็นคลาสที่ใช้กันทั่วไปในแพ็คเกจ JUC มันเป็นแอปพลิเคชันของโหมดการแชร์ AQS มันสามารถอนุญาตให้หลายเธรดทำงานบนทรัพยากรที่ใช้ร่วมกันในเวลาเดียวกันและสามารถควบคุมจำนวนของการเกิดพร้อมกันได้อย่างมีประสิทธิภาพ สามารถควบคุมการจราจรที่ดีได้ Semaphore ให้แนวคิดของใบอนุญาตซึ่งถือได้ว่าเป็นตั๋วรถบัส เฉพาะผู้ที่ได้รับตั๋วสำเร็จเท่านั้นที่จะขึ้นรถบัสได้ มีตั๋วจำนวนหนึ่งและเป็นไปไม่ได้ที่จะออกโดยไม่มีข้อ จำกัด ซึ่งจะนำไปสู่การบรรทุกบัสมากเกินไป ดังนั้นเมื่อออกตั๋ว (รถบัสเต็ม) คนอื่น ๆ สามารถรอรถไฟขบวนต่อไปเท่านั้น หากมีคนออกจากรถบัสไปครึ่งทางตำแหน่งของเขาจะฟรีดังนั้นหากคนอื่นต้องการขึ้นรถบัสในเวลานี้พวกเขาสามารถรับตั๋วได้อีกครั้ง พูลต่าง ๆ สามารถนำไปใช้ได้โดยใช้ Semaphore ในตอนท้ายของบทความนี้เราจะเขียนพูลการเชื่อมต่อฐานข้อมูลอย่างง่าย ก่อนอื่นมาดูตัวสร้างเซมาฟอร์
// constructor 1public semaphore (ใบอนุญาต int) {sync = ใหม่ nonfairsync (ใบอนุญาต);} // constructor 2public semaphore (ใบอนุญาต int, boolean fair) {sync = fair? ใหม่ fairsync (ใบอนุญาต): nonfairsync ใหม่ (ใบอนุญาต);}Semaphore มีตัวสร้างที่ปราศจากพารามิเตอร์สองตัว แต่ไม่มีตัวสร้างที่ปราศจากพารามิเตอร์ ตัวสร้างทั้งสองจะต้องผ่านจำนวนใบอนุญาตเริ่มต้น เซมาฟอร์ที่สร้างขึ้นโดยใช้คอนสตรัคเตอร์ 1 จะได้รับในแบบที่ไม่ได้รับใบอนุญาตเมื่อได้รับใบอนุญาต การใช้คอนสตรัคเตอร์ 2 สามารถระบุวิธีการรับใบอนุญาตผ่านพารามิเตอร์ (ยุติธรรมหรือไม่ยุติธรรม) Semaphore ส่วนใหญ่ให้ API สองประเภทให้กับโลกภายนอกโดยได้รับใบอนุญาตและปล่อยใบอนุญาต ค่าเริ่มต้นคือการได้รับและปล่อยใบอนุญาตหนึ่งใบและพารามิเตอร์สามารถส่งผ่านเพื่อรับและปล่อยสิทธิ์การใช้งานหลายใบในเวลาเดียวกัน ในบทความนี้เราจะพูดคุยเกี่ยวกับสถานการณ์การได้รับและปล่อยใบอนุญาตหนึ่งใบในแต่ละครั้งเท่านั้น
1. รับใบอนุญาต
// รับใบอนุญาต (การตอบสนองขัดจังหวะ) โมฆะสาธารณะที่ได้มา () พ่น InterruptedException {sync.acquiresharedinctibly (1);} // รับใบอนุญาต (ไม่ตอบสนองต่อการขัดจังหวะ) โมฆะสาธารณะได้รับ () {sync.acquireshared (1); sync.nonfairtryacquireshared (1)> = 0;} // พยายามรับใบอนุญาต (การหมดเวลายาว, หน่วย TimeUnit) โยน interruptedException {return sync.tryacquiresharednanos (1, unit.tonanos (หมดเวลา));}API ข้างต้นคือการดำเนินการรับใบอนุญาตเริ่มต้นที่จัดทำโดย Semaphore ได้รับใบอนุญาตเพียงครั้งเดียวในแต่ละครั้งก็เป็นสถานการณ์ทั่วไปในชีวิตจริง นอกเหนือจากการดึงข้อมูลโดยตรงแล้วยังให้ความพยายามในการดึงข้อมูล การดำเนินการดึงข้อมูลโดยตรงอาจบล็อกเธรดหลังจากความล้มเหลวในขณะที่พยายามดึงข้อมูลจะไม่ ควรสังเกตว่าวิธีการ Tryacquire นั้นใช้เพื่อพยายามรับมันอย่างไม่เป็นธรรม สิ่งที่เรามักใช้ในเวลาปกติคือการได้รับใบอนุญาต มาดูกันว่าจะได้รับอย่างไร คุณจะเห็นว่าวิธีการรับโดยตรงเรียก sync.acquiresharedinctibly (1) วิธีนี้เป็นวิธีการใน AQS เราเคยพูดคุยเกี่ยวกับบทความซีรี่ส์ซอร์สโค้ด AQS ลองตรวจสอบอีกครั้ง
// การรับล็อคในโหมดขัดจังหวะ (โหมดที่ใช้ร่วมกัน) เป็นโมฆะสุดท้ายที่เป็นโมฆะ acquiresharedinctibly (int arg) พ่น InterruptedException {// ก่อนกำหนดว่าเธรดจะถูกขัดจังหวะถ้าเป็นเช่นนั้นให้โยนข้อยกเว้นถ้า (thread.interrupted ()) } // 1 ลองรับล็อคถ้า (tryacquireshared (arg) <0) {// 2 หากการได้มาล้มเหลวให้ป้อนวิธีการ doacquiresharedinctibly (arg); -วิธีแรกที่ได้มาอย่างต่อเนื่องคือการเรียกใช้วิธี tryacquireshared เพื่อพยายามรับ Tryacquireshared เป็นวิธีนามธรรมใน AQS ทั้งสองคลาสที่ได้รับ Fairsync และ Nonfairsync ใช้ตรรกะของวิธีนี้ Fairsync ใช้ตรรกะของการซื้อกิจการอย่างเป็นธรรมในขณะที่ Nonfairsync ใช้ตรรกะของการได้มาซึ่งไม่ใช่การซื้อกิจการ
บทคัดย่อคลาสคงที่การซิงค์จะขยาย AbstractqueuedSynchronizer {// พยายามที่จะได้รับ int int สุดท้าย nonfirtryacquireshared (int ซื้อ) {สำหรับ (;;) {// ได้รับใบอนุญาตที่มีอยู่ // รับใบอนุญาตที่เหลืออยู่ int ที่เหลืออยู่ = พร้อมใช้งาน - ซื้อ; // 1. หากเหลือน้อยกว่า 0 ให้ส่งคืนที่เหลือโดยตรง // 2 หากเหลืออยู่มากกว่า 0 ให้อัปเดตสถานะการซิงโครไนซ์ก่อนจากนั้นจึงส่งคืนที่เหลือถ้า (เหลืออยู่ <0 || เปรียบเทียบสเทอร์สเตท (ที่เหลืออยู่)) {return retion ที่เหลือ; }}}} // nonfairsync คลาสสุดท้ายคงที่ nonfairsync ขยายการซิงค์ {ส่วนตัวคงที่สุดท้าย Long SerialVersionUid = -2694183684443567898L; nonfairsync (ใบอนุญาต int) {super (ใบอนุญาต); } // พยายามที่จะได้รับใบอนุญาตที่ได้รับการป้องกัน int tryacquireshared (int ซื้อ) {ส่งคืน nonfirtryacquireshared (ซื้อ); }} // fair synchronizer คงที่คลาสสุดท้าย fairsync ขยายการซิงค์ {ส่วนตัวคงที่สุดท้าย long serialversionuid = 20143388187960009444l; fairsync (ใบอนุญาต int) {super (ใบอนุญาต); } // พยายามที่จะได้รับใบอนุญาตที่ได้รับการป้องกัน int tryacquireshared (int ซื้อ) {สำหรับ (;;) {// ตัดสินว่ามีใครอยู่ด้านหน้าของคิวการซิงโครไนซ์หรือไม่ถ้า (hasqueuedpredpredeades ()) {// ถ้ามีใด ๆ } // รับสิทธิ์ใช้งานที่มีอยู่ใน INT = getState (); // รับใบอนุญาตที่เหลืออยู่ที่เหลืออยู่ = พร้อมใช้งาน - ได้รับ; // 1. หากเหลือน้อยกว่า 0 ให้กลับไปที่เหลือโดยตรง // 2 หากเหลืออยู่มากกว่า 0 สถานะการซิงโครไนซ์จะได้รับการอัปเดตก่อนจากนั้นจะกลับไปยังที่เหลือถ้า (เหลืออยู่ <0 || ComperEandEndSetState (มีอยู่ที่เหลือ)) {return ที่เหลือ; -ควรสังเกตที่นี่ว่าวิธีการ tryacquireshared ของ nonfairsync เรียกวิธีการที่ไม่ได้รับการดูแลโดยตรงซึ่งอยู่ในการซิงค์คลาสหลัก ตรรกะของการล็อคการซื้อกิจการที่ไม่ได้เป็นจริงคือการนำสถานะการซิงโครไนซ์ปัจจุบันออกมาก่อน (สถานะซิงโครนัสแสดงจำนวนใบอนุญาต), ลบพารามิเตอร์ของสถานะการซิงโครไนซ์ปัจจุบัน หากผลลัพธ์ไม่น้อยกว่า 0 ก็พิสูจน์ได้ว่ายังมีใบอนุญาตที่มีอยู่ดังนั้นค่าของสถานะการซิงโครไนซ์จะได้รับการปรับปรุงโดยตรงโดยใช้การดำเนินการ CAS และในที่สุดค่าผลลัพธ์จะถูกส่งคืนโดยไม่คำนึงว่าผลลัพธ์จะน้อยกว่า 0 ที่นี่เราต้องเข้าใจความหมายของค่าผลตอบแทน การกลับมาจำนวนลบหมายความว่าการได้มาล้มเหลวศูนย์หมายความว่าเธรดปัจจุบันจะได้รับสำเร็จ แต่ไม่สามารถรับเธรดที่ตามมาได้อีกต่อไปและจำนวนบวกหมายความว่าเธรดปัจจุบันจะได้รับสำเร็จและสามารถรับเธรดที่ตามมาได้ ลองดูที่รหัสของวิธีการที่ได้รับการดูแลอย่างต่อเนื่อง
// การล็อคที่ได้รับในโหมดขัดจังหวะ (โหมดที่ใช้ร่วมกัน) โมฆะสุดท้าย void acquiresharedinctibly (int arg) พ่น InterruptedException {// ก่อนอื่นกำหนดว่าเธรดจะถูกขัดจังหวะถ้าเป็นเช่นนั้นให้โยนข้อยกเว้นถ้า (thread.interrupted ()) } // 1 พยายามที่จะได้รับจำนวนล็อค // ลบ: ระบุว่าการได้มาล้มเหลว // ค่าศูนย์: ระบุว่าเธรดปัจจุบันได้มาสำเร็จ แต่เธรดที่ตามมาจะไม่ได้รับ // จำนวนบวกอีกต่อไป: ระบุว่าเธรดปัจจุบันได้รับสำเร็จและเธรดที่ตามมาก็สามารถได้รับความสำเร็จหาก หากการได้มาล้มเหลวให้ป้อนวิธีการ doacquiresharedinctibly (arg); -หากที่เหลือที่เหลือน้อยกว่า 0 หมายความว่าการได้มาล้มเหลว ดังนั้น tryacquireshared (arg) <0 จึงเป็นความจริงดังนั้นวิธี doacquiresharedinctibly จะถูกเรียกต่อไป เมื่อเราพูดคุยเกี่ยวกับ AQS มันจะห่อเธรดปัจจุบันลงในโหนดและใส่ลงในหางของคิวการซิงโครไนซ์และเป็นไปได้ที่จะระงับเธรด นี่คือเหตุผลว่าทำไมเธรดจะเข้าคิวและบล็อกเมื่อเหลือน้อยกว่า 0 ถ้าส่วนที่เหลืออยู่> = 0 หมายความว่าเธรดปัจจุบันได้รับสำเร็จแล้ว ดังนั้น tryacquireshared (arg) <0 จึงเป็น flase ดังนั้นวิธี doacquiresharedinctibleruntably จะไม่ถูกเรียกให้บล็อกเธรดปัจจุบันอีกต่อไป ข้างต้นเป็นตรรกะทั้งหมดของการได้มาที่ไม่เป็นธรรม เมื่อการได้มาอย่างยุติธรรมคุณจะต้องเรียกวิธีการ HasqueuedPredEdectors ก่อนหน้านี้เพื่อพิจารณาว่ามีใครบางคนกำลังเข้าคิวในคิวการซิงโครไนซ์หรือไม่ ถ้าเป็นเช่นนั้น Return -1 แสดงโดยตรงว่าการได้มาล้มเหลวมิฉะนั้นขั้นตอนต่อไปนี้จะดำเนินการต่อไปเป็นการซื้อที่ไม่เป็นธรรม
2. ปล่อยใบอนุญาต
// ปล่อยโมฆะสาธารณะใบอนุญาต () {sync.releaseshared (1);}การเรียกวิธีการวางจำหน่ายคือการปล่อยใบอนุญาต การดำเนินการของมันง่ายมากดังนั้นเราจึงเรียกวิธีการ releaseshared ของ AQS มาดูวิธีนี้กันเถอะ
// release Lock Operation (โหมดที่ใช้ร่วมกัน) Public Final Boolean releaseshared (int arg) {// 1 ลองปล่อยล็อคถ้า (tryreleaseshared (arg)) {// 2 หากการเปิดตัวสำเร็จ กลับมาจริง; } return false;}วิธีการ releaseshared ของ AQS เป็นครั้งแรกเรียกใช้วิธี tryreleaseshared เพื่อพยายามปล่อยล็อค ตรรกะการใช้งานของวิธีนี้อยู่ในการซิงค์คลาสย่อย
บทคัดย่อคลาสคงที่การซิงค์จะขยาย AbstractqueuedSynchronizer {... // พยายามที่จะปล่อยการดำเนินการที่ได้รับการป้องกันขั้นสุดท้ายบูลีน tryreaseshared (Int releases) {สำหรับ (;;) {// รับสถานะการซิงโครไนซ์ปัจจุบัน int ปัจจุบัน = getState (); // บวกสถานะการซิงโครไนซ์ปัจจุบันดังต่อไปนี้ int next = current + releases; // หากผลลัพธ์การเพิ่มน้อยกว่าสถานะการซิงโครไนซ์ปัจจุบันจะมีการรายงานข้อผิดพลาดหาก (ถัดไป <ปัจจุบัน) {โยนข้อผิดพลาดใหม่ ("จำนวนใบอนุญาตสูงสุดเกิน"); } // อัปเดตค่าของสถานะการซิงโครไนซ์ในโหมด CAS และส่งคืนจริงหากการอัปเดตสำเร็จไม่เช่นนั้นจะดำเนินการต่อไปหาก (ComperEandEndSetState (ปัจจุบัน, ถัดไป)) {return true; -คุณจะเห็นได้ว่าวิธี tryreleaseshared ใช้ A for loop เพื่อหมุน ขั้นแรกให้รับสถานะการซิงโครไนซ์เพิ่มพารามิเตอร์ที่เข้ามาแล้วอัปเดตสถานะการซิงโครไนซ์ใน CAS หากการอัปเดตสำเร็จให้ส่งคืนจริงและกระโดดออกจากวิธีการ มิฉะนั้นลูปจะดำเนินต่อไปจนกว่าจะประสบความสำเร็จ นี่คือกระบวนการของเซมาฟอร์ที่ปล่อยใบอนุญาต
3. เขียนพูลเชื่อมต่อด้วยตนเอง
รหัสเซมาฟอร์นั้นไม่ซับซ้อนมากนัก การดำเนินการที่ใช้กันทั่วไปคือการได้รับและปล่อยใบอนุญาต ตรรกะการใช้งานของการดำเนินการเหล่านี้ค่อนข้างง่าย แต่สิ่งนี้ไม่ได้ขัดขวางแอปพลิเคชันของเซมาฟอร์ที่แพร่หลาย ต่อไปเราจะใช้ Semaphore เพื่อใช้พูลเชื่อมต่อฐานข้อมูลอย่างง่าย จากตัวอย่างนี้เราหวังว่าผู้อ่านจะมีความเข้าใจที่ลึกซึ้งยิ่งขึ้นเกี่ยวกับการใช้เซมาฟอร์
คลาสสาธารณะ ConnectPool {// การเชื่อมต่อขนาดสระขนาดส่วนตัว // การเชื่อมต่อฐานข้อมูล Collection Private Connect [] เชื่อมต่อ; // สถานะการเชื่อมต่อสถานะการตั้งค่าสถานะบูลีนส่วนตัว [] ConnectFlag; // จำนวนที่เหลืออยู่ของการเชื่อมต่อที่มีอยู่ INT ผันผวนส่วนตัว // Semaphore Semaphore Semaphore; // Constructor Public ConnectPool (ขนาด int) {this.size = size; this.available = size; semaphore = semaphore ใหม่ (ขนาดจริง); เชื่อมต่อ = ใหม่เชื่อมต่อ [ขนาด]; ConnectFlag = บูลีนใหม่ [ขนาด]; initConnects (); } // เริ่มต้นการเชื่อมต่อเป็นโมฆะส่วนตัว initConnects () {// สร้างจำนวนการเชื่อมต่อฐานข้อมูลที่ระบุสำหรับ (int i = 0; i <this.size; i ++) {เชื่อมต่อ [i] = new Connect (); }} // รับการเชื่อมต่อฐานข้อมูลการเชื่อมต่อส่วนตัวที่ซิงโครไนซ์เชื่อมต่อ getConnect () {สำหรับ (int i = 0; i <connectflag.length; i ++) {// โอนคอลเลกชันเพื่อค้นหาการเชื่อมต่อที่ไม่ได้ใช้ถ้า (! ConnectFlag [i]) {// // ลบจำนวนการเชื่อมต่อที่มีอยู่-; System.out.println ("【"+thread.currentthread (). getName ()+"】เพื่อให้ได้จำนวนการเชื่อมต่อที่เหลืออยู่:"+มีอยู่); // ส่งคืนการอ้างอิงการเชื่อมต่อการเชื่อมต่อเชื่อมต่อ [i]; }} return null; } // รับการเชื่อมต่อสาธารณะเชื่อมต่อ openConnect () พ่น InterruptedException {// รับ semaphore.acquire (); // รับการเชื่อมต่อฐานข้อมูลส่งคืน getConnect (); } // ปล่อยการเชื่อมต่อการเชื่อมต่อสาธารณะที่ซิงโครไนซ์รีลีส (เชื่อมต่อเชื่อมต่อ) {สำหรับ (int i = 0; i <this.size; i ++) {ถ้า (เชื่อมต่อ == เชื่อมต่อ [i]) {// ตั้งค่าการเชื่อมต่อที่ไม่ได้ใช้ ConnectFlag [i] = false; // เพิ่ม 1 หมายเลขการเชื่อมต่อที่มีอยู่; System.out.println ("【"+thread.currentthread (). getName ()+"] เพื่อปล่อยหมายเลขการเชื่อมต่อที่เหลือ:"+มีอยู่); // release semaphore.release (); }}} // เพิ่มจำนวนการเชื่อมต่อที่มีอยู่สาธารณะ int ที่มีอยู่ () {return พร้อมใช้งาน; -รหัสทดสอบ:
Public Class TestThread ขยายเธรด {Private Static ConnectPool Pool = New ConnectPool (3); @Override โมฆะสาธารณะ Run () {ลอง {Connect Connect = pool.openconnect (); Thread.sleep (100); // ใช้สระว่ายน้ำพักรีลีส (เชื่อมต่อ); } catch (interruptedException e) {e.printStackTrace (); }} โมฆะคงที่สาธารณะหลัก (สตริง [] args) {สำหรับ (int i = 0; i <10; i ++) {testThread ใหม่ (). start (); -ผลการทดสอบ:
เราใช้อาร์เรย์เพื่อจัดเก็บการอ้างอิงไปยังการเชื่อมต่อฐานข้อมูล เมื่อเริ่มต้นพูลการเชื่อมต่อเราจะเรียกวิธีการเริ่มต้นเพื่อสร้างจำนวนการเชื่อมต่อฐานข้อมูลที่ระบุและจัดเก็บข้อมูลอ้างอิงในอาร์เรย์ นอกจากนี้ยังมีอาร์เรย์ที่มีขนาดเท่ากันเพื่อบันทึกว่ามีการเชื่อมต่อหรือไม่ เมื่อใดก็ตามที่มีการร้องขอเธรดภายนอกเพื่อรับการเชื่อมต่อวิธีการโทรครั้งแรก Semaphore.Acquire () เพื่อรับใบอนุญาตจากนั้นตั้งค่าสถานะการเชื่อมต่อเป็นที่ใช้งานและในที่สุดก็ส่งคืนการอ้างอิงไปยังการเชื่อมต่อ จำนวนใบอนุญาตถูกกำหนดโดยพารามิเตอร์ที่ส่งผ่านในระหว่างการก่อสร้าง จำนวนใบอนุญาตจะลดลง 1 ทุกครั้งที่มีการเรียกวิธี Semaphore.Acquire () เมื่อจำนวนลดลงเป็น 0 หมายความว่าไม่มีการเชื่อมต่อ ในเวลานี้ถ้าเธรดอื่นรับอีกครั้งมันจะถูกบล็อก เมื่อใดก็ตามที่เธรดปล่อยการเชื่อมต่อ semaphore.release () จะถูกเรียกให้ปล่อยใบอนุญาต ในเวลานี้จำนวนใบอนุญาตทั้งหมดจะเพิ่มขึ้นอีกครั้งซึ่งหมายความว่าจำนวนการเชื่อมต่อที่มีอยู่เพิ่มขึ้น จากนั้นเธรดที่ถูกบล็อกก่อนหน้านี้จะตื่นขึ้นมาและยังคงได้รับการเชื่อมต่อ ในเวลานี้คุณสามารถรับการเชื่อมต่อได้สำเร็จโดยได้รับอีกครั้ง ในตัวอย่างการทดสอบพูลการเชื่อมต่อของการเชื่อมต่อ 3 ครั้งจะเริ่มต้น เราสามารถเห็นได้จากผลการทดสอบว่าเมื่อใดก็ตามที่เธรดได้รับการเชื่อมต่อจำนวนการเชื่อมต่อที่เหลือจะลดลง 1 เมื่อเธรดลดลงเหลือ 0 เธรดอื่น ๆ จะไม่สามารถรับได้อีกต่อไป ในเวลานี้คุณต้องรอให้เธรดปล่อยการเชื่อมต่อก่อนที่จะได้รับต่อไป คุณจะเห็นว่าจำนวนการเชื่อมต่อที่เหลืออยู่เสมอจะเปลี่ยนแปลงระหว่าง 0 ถึง 3 ซึ่งหมายความว่าการทดสอบของเราประสบความสำเร็จ
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น