ใน [Java II ที่เกิดขึ้นพร้อมกันสูง] มูลนิธิมัลติเธรดมัลติเธรดเราได้กล่าวถึงการดำเนินการซิงโครไนซ์ขั้นพื้นฐานในขั้นต้น สิ่งที่เราต้องการพูดถึงในเวลานี้คือเครื่องมือควบคุมการซิงโครไนซ์ในแพ็คเกจพร้อมกัน
1. การใช้เครื่องมือควบคุมการซิงโครไนซ์ต่างๆ
1.1 reentrantlock
Reentrantlock ให้ความรู้สึกเหมือนเป็นรุ่นที่ได้รับการปรับปรุง คุณลักษณะของการซิงโครไนซ์คือการใช้งานง่ายและทุกอย่างถูกทิ้งไว้กับ JVM สำหรับการประมวลผล แต่ฟังก์ชั่นของมันค่อนข้างอ่อนแอ ก่อน JDK1.5 ประสิทธิภาพของ Reentrantlock นั้นดีกว่าการซิงโครไนซ์ เนื่องจากการเพิ่มประสิทธิภาพของ JVM ประสิทธิภาพของทั้งสองในเวอร์ชัน JDK ปัจจุบันจะเทียบได้ หากเป็นการใช้งานที่เรียบง่ายอย่าใช้ reentrantlock โดยเจตนา
เมื่อเปรียบเทียบกับการซิงโครไนซ์ reentrantlock นั้นอุดมไปด้วยการใช้งานได้ดีกว่าและมีลักษณะของ reentrant, ขัดจังหวะ, เวลา จำกัด และการล็อคที่เป็นธรรม
ก่อนอื่นให้ใช้ตัวอย่างเพื่อแสดงให้เห็นถึงการใช้งานครั้งแรกของ reentrantlock:
การทดสอบแพ็คเกจ; นำเข้า java.util.concurrent.locks.reentrantlock; การทดสอบระดับสาธารณะใช้งาน {สาธารณะคงที่ reentrantlock ล็อค = ใหม่ reentrantlock (); สาธารณะคงที่ int i = 0; @Override โมฆะสาธารณะ Run () {สำหรับ (int j = 0; j <10000000; j ++) {lock.lock (); ลอง {i ++; } ในที่สุด {lock.unlock (); }}} โมฆะคงที่สาธารณะหลัก (String [] args) พ่น InterruptedException {ทดสอบทดสอบ = การทดสอบใหม่ (); เธรด t1 = เธรดใหม่ (ทดสอบ); เธรด t2 = เธรดใหม่ (ทดสอบ); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); -มีสองเธรดที่ดำเนินการ ++ ใน i เพื่อให้แน่ใจว่ามีความปลอดภัยของด้าย reentrantlock จะใช้ จากการใช้งานเราจะเห็นได้ว่าเมื่อเทียบกับการซิงโครไนซ์ reentrantlock นั้นซับซ้อนกว่าเล็กน้อย เนื่องจากการดำเนินการปลดล็อคจะต้องดำเนินการในที่สุดหากไม่ได้ปลดล็อคในที่สุดก็เป็นไปได้ว่ารหัสมีข้อยกเว้นและการล็อคไม่ได้ถูกปล่อยออกมาและการซิงโครไนซ์จะถูกปล่อยออกมาโดย JVM
แล้วคุณลักษณะที่ยอดเยี่ยมของ reentrantlock คืออะไร?
1.1.1 เข้ามาอีกครั้ง
สามารถป้อนเธรดเดี่ยวซ้ำ ๆ ได้ แต่ต้องออกซ้ำ ๆ
lock.lock (); lock.lock (); ลอง {i ++; } ในที่สุด {lock.unlock (); lock.unlock ();}เนื่องจาก reentrantlock เป็นล็อคใหม่คุณสามารถล็อคเดียวกันซ้ำ ๆ ซ้ำ ๆ ซึ่งมีตัวนับการซื้อที่เกี่ยวข้องกับการล็อค หากเธรดที่เป็นเจ้าของล็อคจะได้รับการล็อคอีกครั้งตัวนับการซื้อจะเพิ่มขึ้น 1 และล็อคจะต้องปล่อยสองครั้งเพื่อให้ได้การเปิดตัวจริง (ล็อคอีกครั้ง) การเลียนแบบความหมายของการซิงโครไนซ์; หากเธรดเข้าสู่บล็อกที่ซิงโครไนซ์ป้องกันโดยจอภาพว่าเธรดมีอยู่แล้วเธรดจะได้รับอนุญาตให้ดำเนินการต่อ เมื่อเธรดออกจากบล็อกซิงโครไนซ์ที่สอง (หรือตามมา) การล็อคจะไม่ถูกปล่อยออกมา ล็อคจะถูกปล่อยออกมาเฉพาะเมื่อเธรดออกจากบล็อกที่ซิงโครไนซ์แรกที่ได้รับการป้องกันโดยจอภาพที่เข้ามา
เด็กชั้นเรียนสาธารณะขยายพ่อดำเนินการ {เด็กคงที่สุดท้าย = เด็กใหม่ (); // เพื่อให้แน่ใจว่าล็อคโมฆะคงที่สาธารณะที่ไม่ซ้ำกันหลัก (สตริง [] args) {สำหรับ (int i = 0; i <50; i ++) {เธรดใหม่ (เด็ก) .start (); }} โมฆะที่ซิงโครไนซ์สาธารณะ dosomething () {system.out.println ("1child.dosomething ()"); Doanhothing (); // โทรหาวิธีที่ซิงโครไนซ์อื่น ๆ ในคลาสของคุณเอง} โมฆะแบบซิงโครไนซ์ส่วนตัว doanothing () {super.dosomething (); // โทรหาวิธีการซิงโครไนซ์ของระบบพาเรนต์ System.out.println ("3child.doanotherthing ()"); } @Override โมฆะสาธารณะ Run () {child.dosomething (); }} ชั้นเรียนพ่อ {โมฆะที่ซิงโครไนซ์สาธารณะ dosomething () {system.out.println ("2father.dosomething ()"); -เราจะเห็นว่าเธรดเข้าสู่วิธีการซิงโครไนซ์ที่แตกต่างกันและจะไม่ปล่อยล็อคที่ได้รับมาก่อน ดังนั้นเอาต์พุตยังคงเป็นลำดับ ดังนั้นการซิงโครไนซ์จึงเป็นล็อคอีกครั้ง
เอาท์พุท:
1child.dosomething ()
2father.dosomething ()
3child.doanotherthing ()
1child.dosomething ()
2father.dosomething ()
3child.doanotherthing ()
1child.dosomething ()
2father.dosomething ()
3child.doanotherthing ()
-
1.1.2. ถูกขัดจังหวะ
ซึ่งแตกต่างจากการซิงโครไนซ์ reentrantlock ตอบสนองต่อการขัดจังหวะ มุมมองความรู้ที่เกี่ยวข้องกับการขัดจังหวะ [การพร้อมกันสูง Java 2] พื้นฐานมัลติเธรด
Lock.lock () ธรรมดาไม่สามารถตอบสนองต่อการขัดจังหวะ Lock.lockinctibly () สามารถตอบสนองต่อการขัดจังหวะ
เราจำลองฉากการหยุดชะงักแล้วใช้การขัดจังหวะเพื่อจัดการกับการหยุดชะงัก
การทดสอบแพ็คเกจ; นำเข้า java.lang.Management.ManagementFactory; นำเข้า java.lang.Management.threadinfo; นำเข้า java.lang.Management.ThreadMxBean; นำเข้า java.util.concurrent.locks.reentrantlock; การทดสอบระดับสาธารณะ สาธารณะคงที่ reentrantlock lock2 = ใหม่ reentrantlock (); ล็อค int; การทดสอบสาธารณะ (int lock) {this.lock = ล็อค; } @Override โมฆะสาธารณะเรียกใช้ () {ลอง {ถ้า (ล็อค == 1) {lock1.lockinctinructably (); ลอง {thread.sleep (500); } catch (exception e) {// todo: จัดการข้อยกเว้น} lock2.lockinctibly (); } else {lock2.lockinctibly (); ลอง {thread.sleep (500); } catch (exception e) {// todo: จัดการข้อยกเว้น} lock1.lockinctibly (); }} catch (exception e) {// todo: จัดการข้อยกเว้น} ในที่สุด {ถ้า (lock1.isheldbycurrentthread ()) {lock1.unlock (); } if (lock2.isheldbycurrentthread ()) {lock2.unlock (); } system.out.println (thread.currentthread (). getId () + ": ออกจากเธรด"); }} โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่น InterruptedException {ทดสอบ t1 = การทดสอบใหม่ (1); ทดสอบ T2 = การทดสอบใหม่ (2); เธรดเธรด 1 = เธรดใหม่ (T1); เธรดเธรด 2 = เธรดใหม่ (T2); Thread1.start (); Thread2.start (); Thread.sleep (1,000); //deadlockchecker.check (); } คลาสคงที่ deadlockChecker {ส่วนตัวสุดท้ายคงที่ด้ายสแตติก mBean = ManagementFactory .getThreadMxBean (); ขั้นสุดท้ายคงที่ runnable deadlockChecker = new runnable () {@Override โมฆะสาธารณะเรียกใช้ () {// toDo วิธีการที่สร้างขึ้นอัตโนมัติในขณะที่ (จริง) {ยาว [] doallockedThreadids = mBean.findDeadLockedThreads (); if (deadlockedThreadids! = null) {threadInfo [] threadInfos = mBean.getThreadInfo (DeadlockedThreadids); สำหรับ (เธรด t: thread.getAllStackTraces (). keyset ()) {สำหรับ (int i = 0; i <threadInfos.length; i ++) {ถ้า (t.getId () == threadInfos [i] .getThreadId ()) }}}}} ลอง {thread.sleep (5000); } catch (exception e) {// todo: จัดการข้อยกเว้น}}}}}; การตรวจสอบโมฆะคงที่สาธารณะ () {เธรด t = เธรดใหม่ (DeadlockChecker); T.Setdaemon (จริง); T.Start (); -รหัสข้างต้นอาจทำให้เกิดการหยุดชะงักด้าย 1 ได้รับ Lock1, Thread 2 ได้รับ Lock2 จากนั้นกันและกันต้องการล็อคของกันและกัน
เราใช้ JStack เพื่อดูสถานการณ์หลังจากเรียกใช้รหัสด้านบน
การหยุดชะงักถูกค้นพบอย่างแน่นอน
deadlockchecker.check (); วิธีการใช้ในการตรวจจับการหยุดชะงักแล้วขัดจังหวะเธรดการหยุดชะงัก หลังจากหยุดชะงักเธรดจะออกตามปกติ
1.1.3. จำกัด เวลา
หากการหมดเวลาไม่สามารถรับล็อคได้มันจะกลับเท็จและจะไม่รออย่างถาวรเพื่อสร้างล็อคที่ตายแล้ว
ใช้ Lock.Trylock (Long Timeout, TimeUnit Unit) เพื่อใช้การล็อคที่ใช้เวลาไม่ได้โดยมีพารามิเตอร์เป็นเวลาและหน่วย
ให้ฉันยกตัวอย่างให้คุณเห็นว่าเวลาอาจมี จำกัด :
การทดสอบแพ็คเกจ; นำเข้า java.util.concurrent.timeUnit; นำเข้า java.util.concurrent.locks.reentrantlock; การทดสอบระดับสาธารณะใช้งานได้ @Override โมฆะสาธารณะ Run () {ลอง {ถ้า (lock.trylock (5, timeunit.seconds)) {thread.sleep (6000); } else {system.out.println ("รับล็อคล้มเหลว"); }} catch (exception e) {} ในที่สุด {if (lock.isheldbycurrentthread ()) {lock.unlock (); }}} โมฆะคงที่สาธารณะหลัก (สตริง [] args) {ทดสอบ t = การทดสอบใหม่ (); เธรด t1 = เธรดใหม่ (t); เธรด t2 = เธรดใหม่ (t); t1.start (); t2.start (); -ใช้สองเธรดเพื่อแข่งขันเพื่อล็อค เมื่อเธรดได้รับล็อคให้นอน 6 วินาทีและแต่ละเธรดพยายามที่จะรับล็อคเป็นเวลา 5 วินาทีเท่านั้น
ดังนั้นจะต้องมีเธรดที่ไม่สามารถรับล็อคได้ หากคุณไม่สามารถรับได้คุณจะออกโดยตรง
เอาท์พุท:
รับล็อคล้มเหลว
1.1.4. ล็อคที่ยุติธรรม
วิธีใช้:
Public Reentrantlock (Boolean Fair)
สาธารณะ reentrantlock fairlock = ใหม่ reentrantlock (จริง);
ล็อคโดยทั่วไปไม่ยุติธรรม เป็นไปไม่ได้ว่าเธรดที่มาก่อนจะได้รับการล็อคก่อน แต่เธรดที่มาในภายหลังจะได้รับการล็อคในภายหลัง ล็อคที่ไม่เป็นธรรมอาจทำให้เกิดความหิว
การล็อคที่เป็นธรรมหมายความว่าล็อคนี้สามารถมั่นใจได้ว่าเธรดมาก่อนและรับการล็อคก่อน แม้ว่าการล็อคที่เป็นธรรมจะไม่ทำให้เกิดความหิว แต่ประสิทธิภาพของล็อคที่เป็นธรรมจะเลวร้ายยิ่งกว่าล็อคที่ไม่ใช่คู่ล็อค
1.2 เงื่อนไข
ความสัมพันธ์ระหว่างเงื่อนไขและ reentrantlock นั้นคล้ายกับการซิงโครไนซ์และ Object.wait ()/สัญญาณ ()
วิธีการรอคอย () จะทำให้เธรดปัจจุบันรอและปล่อยล็อคปัจจุบัน เมื่อใช้สัญญาณ () ในเธรดอื่นหรือเมธอด SignalAll () เธรดจะกลับมาล็อคและดำเนินการต่อไป หรือเมื่อเธรดถูกขัดจังหวะคุณสามารถกระโดดออกจากการรอคอยได้ สิ่งนี้คล้ายกับวิธีการ Object.wait ()
วิธีการรอคอยอย่างต่อเนื่อง () นั้นเหมือนกับวิธีการรอคอย () แต่จะไม่รอการตอบสนองการตอบสนองในระหว่างกระบวนการ วิธี Singal () ใช้ในการปลุกเธรดรอ วิธี Singalall () สัมพัทธ์จะตื่นขึ้นมาทุกกระทู้ที่รออยู่ สิ่งนี้คล้ายกับวิธี OBJCT.Notify ()
ฉันจะไม่แนะนำรายละเอียดที่นี่ ให้ฉันยกตัวอย่างให้คุณเห็น:
การทดสอบแพ็คเกจ; นำเข้า java.util.concurrent.locks.condition; นำเข้า java.util.concurrent.locks.reentrantlock; การทดสอบระดับสาธารณะใช้งานได้ เงื่อนไขคงที่สาธารณะ = lock.newCondition (); @Override โมฆะสาธารณะ Run () {ลอง {lock.lock (); เงื่อนไข Aawait (); System.out.println ("เธรดกำลังเกิดขึ้น"); } catch (exception e) {e.printstacktrace (); } ในที่สุด {lock.unlock (); }} โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่น InterruptedException {ทดสอบ t = การทดสอบใหม่ (); เธรดเธรด = เธรดใหม่ (t); thread.start (); Thread.sleep (2000); lock.lock (); เงื่อนไข. signal (); lock.unlock (); -ตัวอย่างข้างต้นนั้นง่ายมาก ให้เธรดรอและปล่อยให้เธรดหลักปลุกขึ้นมา เงื่อนไขการรอคอย ()/สัญญาณสามารถใช้งานได้หลังจากได้รับการล็อคเท่านั้น
1.3.Semaphore
สำหรับล็อคมันเป็นเอกสิทธิ์เฉพาะบุคคล หมายความว่าตราบใดที่ฉันได้ล็อคไม่มีใครสามารถรับได้อีก
สำหรับ Semaphore จะอนุญาตให้หลายเธรดเข้าสู่ส่วนวิกฤตในเวลาเดียวกัน มันถือได้ว่าเป็นล็อคที่ใช้ร่วมกัน แต่ขีด จำกัด ที่ใช้ร่วมกันมี จำกัด หลังจากที่ขีด จำกัด หมดแล้วเธรดอื่น ๆ ที่ไม่ได้รับขีด จำกัด จะยังคงปิดกั้นนอกพื้นที่วิกฤต เมื่อจำนวนเงินเป็น 1 มันจะเทียบเท่ากับล็อค
นี่คือตัวอย่าง:
การทดสอบแพ็คเกจ; นำเข้า java.util.concurrent.executorservice; นำเข้า java.util.concurrent.executors; นำเข้า java.util.concurrent.semaphore; การทดสอบระดับสาธารณะ @Override โมฆะสาธารณะ Run () {ลอง {semaphore.acquire (); Thread.sleep (2000); System.out.println (thread.currentthread (). getId () + "เสร็จสิ้น"); } catch (exception e) {e.printstacktrace (); } ในที่สุด {semaphore.release (); }} โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่น InterruptedException {ExecutorService ExecutorService = Executors.newFixedThreadPool (20); การทดสอบขั้นสุดท้าย t = การทดสอบใหม่ (); สำหรับ (int i = 0; i <20; i ++) {executorService.submit (t); -มีพูลเธรดที่มี 20 เธรดและแต่ละเธรดจะไปที่ใบอนุญาตของ Semaphore มีใบอนุญาตเพียง 5 ใบสำหรับเซมาฟอร์ หลังจากทำงานแล้วคุณจะเห็นว่า 5 เป็นเอาต์พุตเป็นแบตช์แบทช์จะถูกส่งออก
แน่นอนว่าหนึ่งเธรดสามารถสมัครใบอนุญาตหลายใบพร้อมกันได้
โมฆะสาธารณะได้มา (ใบอนุญาต int) พ่น InterruptedException
1.4 ReadWriteLock
ReadWriteLock เป็นล็อคที่แยกฟังก์ชัน การอ่านและการเขียนเป็นสองฟังก์ชั่นที่แตกต่างกัน: การอ่านการอ่านไม่ได้เกิดขึ้นร่วมกันการอ่าน-เขียนเป็นเอกสิทธิ์เฉพาะบุคคลและการเขียนเขียนเป็นเอกสิทธิ์เฉพาะบุคคล
การออกแบบนี้จะเพิ่มการเกิดขึ้นพร้อมกันและสร้างความมั่นใจในความปลอดภัยของข้อมูล
วิธีใช้:
Private Static ReentRantReadWriteLock ReadWriteLock = ใหม่ reentRantReadWriteLock ();
ล็อคแบบคงที่ส่วนตัว readlock = readWriteLock.readlock ();
ล็อคแบบคงที่ส่วนตัว writeLock = readWriteLock.writeLock ();
สำหรับตัวอย่างโดยละเอียดคุณสามารถดูการใช้งาน Java ของผู้ผลิตและปัญหาผู้บริโภคและปัญหาผู้อ่านและนักเขียนและฉันจะไม่ขยายที่นี่
1.5 Countdownlatch
สถานการณ์ทั่วไปสำหรับตัวจับเวลานับถอยหลังคือการเปิดตัวจรวด ก่อนที่จะมีการเปิดตัวจรวดเพื่อให้แน่ใจว่าทุกอย่างนั้นสามารถเข้าใจผิดได้การตรวจสอบอุปกรณ์และเครื่องมือต่าง ๆ มักจะดำเนินการ เครื่องยนต์สามารถติดไฟได้หลังจากการตรวจสอบทั้งหมดเสร็จสมบูรณ์ สถานการณ์นี้เหมาะสำหรับ Countdownlatch มันสามารถทำให้เธรดจุดระเบิดรอให้เธรดตรวจสอบทั้งหมดเสร็จสมบูรณ์ก่อนที่จะดำเนินการ
วิธีใช้:
สุดท้าย countdownlatch end = new countdownlatch (10);
end.countdown ();
end.await ();
แผนผังไดอะแกรม:
ตัวอย่างง่ายๆ:
การทดสอบแพ็คเกจ; นำเข้า java.util.concurrent.countdownlatch; นำเข้า java.util.concurrent.executorservice; นำเข้า java.util.concurrent.executors; การทดสอบระดับสาธารณะ การทดสอบขั้นสุดท้ายคงที่ t = การทดสอบใหม่ (); @Override โมฆะสาธารณะ Run () {ลอง {thread.sleep (2000); System.out.println ("เสร็จสมบูรณ์"); countdownlatch.countdown (); } catch (exception e) {e.printstacktrace (); }} โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่น InterruptedException {ExecutorService ExecutorService = Executors.NewFixedThreadPool (10); สำหรับ (int i = 0; i <10; i ++) {executorService.execute (t); } countdownlatch.await (); System.out.println ("end"); ExecutorService.Shutdown (); -เธรดหลักจะต้องรอให้ทั้ง 10 เธรดดำเนินการก่อนที่จะส่งออก "end"
1.6 Cyclicbarrier
คล้ายกับ Countdownlatch นอกจากนี้ยังรอให้บางเธรดเสร็จสมบูรณ์ก่อนที่จะดำเนินการ ความแตกต่างกับ Countdownlatch คือตัวนับนี้สามารถใช้ซ้ำได้ ตัวอย่างเช่นสมมติว่าเราตั้งค่าตัวนับเป็น 10 จากนั้นหลังจากรวบรวมชุดแรกของ 10 เธรดตัวนับจะกลับเป็นศูนย์จากนั้นรวบรวมชุดถัดไป 10 เธรดถัดไป
วิธีใช้:
Public Cyclicbarrier (Int Parties, Runnable Barrieraction)
Barrieraction คือการกระทำที่ระบบจะดำเนินการเมื่อนับนับได้หนึ่งครั้ง
รอ ()
แผนผังไดอะแกรม:
นี่คือตัวอย่าง:
การทดสอบแพ็คเกจ; นำเข้า java.util.concurrent.cyclicbarrier; การทดสอบระดับสาธารณะใช้งานการใช้งาน {String Soldier ส่วนตัว; Cyclicbarrier Cyclic ส่วนตัวสุดท้าย; การทดสอบสาธารณะ (String Soldier, CyclicBarrier Cyclic) {this.soldier = Soldier; this.cyclic = cyclic; } @Override โมฆะสาธารณะ Run () {ลอง {// รอให้ทหารทุกคนมาถึง cyclic.await (); Dowork (); // รอให้ทหารทุกคนทำงาน Cyclic.await () ให้เสร็จสมบูรณ์; } catch (exception e) {// todo บล็อก catch block ที่สร้างอัตโนมัติ e.printstacktrace (); }} โมฆะส่วนตัว Dowork () {// todo วิธีการที่สร้างขึ้นอัตโนมัติ stub ลอง {thread.sleep (3000); } catch (Exception e) {// todo: จัดการข้อยกเว้น} system.out.println (ทหาร + ": เสร็จสิ้น"); } Barrierrun คลาสคงที่ใช้งานได้ amplanable {ธงบูลีน; int n; Public BarrierRun (ธงบูลีน, int n) {super (); this.flag = Flag; this.n = n; } @Override โมฆะสาธารณะ Run () {ถ้า (FLAG) {System.out.println (n + "งานเสร็จ"); } else {system.out.println (n + "set completeion"); ธง = จริง; }}} โมฆะคงที่สาธารณะหลัก (สตริง [] args) {int สุดท้าย n = 10; เธรด [] เธรด = เธรดใหม่ [n]; ธงบูลีน = เท็จ; CyclicBarrier Barrier = New CyclicBarrier (N, New BarrierRun (Flag, N)); System.out.println ("set"); สำหรับ (int i = 0; i <n; i ++) {system.out.println (i+"รายงาน"); เธรด [i] = เธรดใหม่ (การทดสอบใหม่ ("ทหาร" + i, สิ่งกีดขวาง)); เธรด [i]. start (); -ผลการพิมพ์:
รวมตัว
0 รายงาน
1 รายงาน
2 รายงาน
3 รายงาน
4 รายงาน
5 รายงาน
6 รายงาน
7 รายงาน
8 รายงาน
9 รายงาน
10 ชุดที่สมบูรณ์ทหาร 5: เสร็จสิ้น
ทหาร 7: เสร็จสิ้น
ทหาร 8: ทำเสร็จแล้ว
ทหาร 3: ทำเสร็จแล้ว
ทหาร 4: เสร็จแล้ว
ทหาร 1: เสร็จแล้ว
ทหาร 6: ทำเสร็จแล้ว
ทหาร 2: ทำเสร็จแล้ว
ทหาร 0: เสร็จสิ้น
ทหาร 9: เสร็จแล้ว
10 งานเสร็จสิ้น
1.7 locksupport
ให้ด้ายบล็อกดั้งเดิม
คล้ายกับการระงับ
lockksupport.park ();
locksupport.unpark (T1);
เมื่อเทียบกับการระงับมันไม่ใช่เรื่องง่ายที่จะทำให้เกิดการแช่แข็งด้าย
แนวคิดของ Locksupport ค่อนข้างคล้ายกับ Semaphore มันมีใบอนุญาตภายใน จะใช้ใบอนุญาตนี้เมื่อจอดรถและใช้สำหรับใบอนุญาตนี้เมื่อไม่ได้ทำอะไรเลย ดังนั้นหาก unpark อยู่ก่อนที่จะจอดรถจะไม่เกิดการแช่แข็งเธรด
รหัสต่อไปนี้คือรหัสตัวอย่างที่ระงับไว้ใน [High Concurrency Java 2] มูลนิธิมัลติเธรดมัลติเธรด การหยุดชะงักเกิดขึ้นเมื่อใช้ระงับ
การทดสอบแพ็คเกจ; นำเข้า java.util.concurrent.locks.locksupport; การทดสอบระดับสาธารณะ {วัตถุคงที่ u = วัตถุใหม่ (); Static TestSuspendThread T1 = New TestSuspendThread ("T1"); Static TestSuspendThread T2 = TestSuspendThread ใหม่ ("T2"); Public Static Class TestSuspendThread ขยายเธรด {Public TestSusPendThread (ชื่อสตริง) {setName (ชื่อ); } @Override โมฆะสาธารณะเรียกใช้ () {ซิงโครไนซ์ (u) {system.out.println ("in" + getName ()); //thread.currentthread (). suspend (); lockksupport.park (); }}} โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่น InterruptedException {t1.start (); Thread.sleep (100); t2.start (); // t1.resume (); // t2.resume (); locksupport.unpark (T1); locksupport.unpark (T2); t1.join (); t2.join (); -อย่างไรก็ตามการใช้ locksupport จะไม่ทำให้เกิดการหยุดชะงัก
นอกจากนี้
Park () สามารถตอบสนองต่อการขัดจังหวะ แต่ไม่ได้ทำข้อยกเว้น ผลลัพธ์ของการตอบสนองขัดจังหวะคือฟังก์ชั่นการกลับมาของสวนสาธารณะ () สามารถรับธงขัดจังหวะจากเธรด interrupted ()
มีหลายสถานที่ใน JDK ที่ใช้ Park แน่นอนการใช้งาน Locksupport ยังถูกนำมาใช้โดยใช้ unserai.park ()
Public Static Void Park () {
unsafe.park (false, 0l);
-
1.8 การใช้งาน reentrantlock
มาแนะนำการใช้งาน Reentrantlock การดำเนินการของ reentrantlock ส่วนใหญ่ประกอบด้วยสามส่วน:
คลาสแม่ของ Reentrantlock จะมีตัวแปรสถานะเพื่อแสดงสถานะซิงโครนัส
/*** สถานะการซิงโครไนซ์ */ สถานะ int ผันผวนส่วนตัว;
ตั้งสถานะเพื่อรับการล็อคผ่านการดำเนินการ CAS หากตั้งค่าเป็น 1 ตัวยึดล็อคจะถูกมอบให้กับเธรดปัจจุบัน
void lock สุดท้าย () {if (pompereanDsetState (0, 1)) setExClusiveOwnerThread (thread.currentthread ()); ได้รับ (1); -หากล็อคไม่สำเร็จจะมีการใช้แอปพลิเคชัน
โมฆะสุดท้ายสาธารณะได้รับ (int arg) {ถ้า (! tryacquire (arg) && acquirequeued (addwaiter (node.exclusive), arg)) selfinterrupt (); -ก่อนอื่นลอง Tryacquire หลังจากสมัครเพราะเธรดอื่นอาจปล่อยล็อค
หากคุณยังไม่ได้ใช้สำหรับล็อคให้เพิ่มบริกรซึ่งหมายถึงการเพิ่มตัวเองลงในคิวรอ
โหนดส่วนตัว addWaiter (โหมดโหนด) {node node = new node (thread.currentthread (), โหมด); // ลองเส้นทางที่รวดเร็วของ Enq; สำรองข้อมูลเต็ม enq บนโหนดความล้มเหลว pred = tail; if (pred! = null) {node.prev = pred; if (เปรียบเทียบ Settail (pred, node)) {pred.next = node; ส่งคืนโหนด; }} enq (โหนด); ส่งคืนโหนด; -ในช่วงเวลานี้จะมีความพยายามมากมายที่จะสมัครล็อคและหากคุณยังไม่สามารถสมัครได้คุณจะถูกวางสาย
Private Final Boolean ParkandCheckInterrupt () {lockksupport.park (นี่); return thread.interrupted (); -ในทำนองเดียวกันหากล็อคถูกปล่อยออกมาและไม่ได้กล่าวถึงรายละเอียดที่นี่
2. คอนเทนเนอร์พร้อมกันและการวิเคราะห์ซอร์สโค้ดทั่วไป
2.1 พร้อมกัน
เรารู้ว่า HashMap ไม่ใช่คอนเทนเนอร์ที่ปลอดภัยของเธรด วิธีที่ง่ายที่สุดในการทำ HashMap Thread-Safe คือการใช้งาน
คอลเลกชัน SynchronizedMap มันเป็น wrapper สำหรับ hashmap
แผนที่สาธารณะคงที่ m = คอลเลกชัน SynchronizedMap (ใหม่ hashmap ());
ในทำนองเดียวกันสำหรับรายการชุดยังมีวิธีการที่คล้ายกัน
อย่างไรก็ตามวิธีนี้เหมาะสำหรับกรณีที่จำนวนการเกิดพร้อมกันค่อนข้างเล็ก
ลองมาดูการใช้งาน SynchronizedMap
แผนที่สุดท้ายส่วนตัว <k, v> m; // การสำรองแผนที่วัตถุสุดท้าย mutex; // วัตถุที่จะซิงโครไนซ์ SynchronizedMap (แผนที่ <k, v> m) {ถ้า (m == null) โยน nullpointerexception ใหม่ (); this.m = m; mutex = this; } synchronizedMap (แผนที่ <k, v> m, Object mutex) {this.m = m; this.mutex = mutex; } ขนาด int สาธารณะ () {ซิงโครไนซ์ (mutex) {return m.size ();}} บูลีนสาธารณะ isempty () {ซิงโครไนซ์ (mutex) {return m.isempty ();}} boolean publickey ซิงโครไนซ์ (mutex) {return m.containsValue (ค่า);}} สาธารณะ v รับ (คีย์วัตถุ) {ซิงโครไนซ์ (mutex) {return m.get (key);}} public v ใส่ (k key, v value) {synchronized (mutex) {return m.put m.remove (key);}} โมฆะสาธารณะ putall (แผนที่ <? ขยาย k,? ขยาย v> map) {ซิงโครไนซ์ (mutex) {m.putall (แผนที่);}} โมฆะสาธารณะล้าง () {ซิงโครไนซ์ (mutex) {m.clear ();}}};มันห่อ HashMap ไว้ข้างในแล้วซิงโครไนซ์ทุกการทำงานของ HashMap
เนื่องจากแต่ละวิธีได้รับการล็อคเดียวกัน (mutex) ซึ่งหมายความว่าการดำเนินการเช่นใส่และลบเป็นพิเศษร่วมกันลดจำนวนของการเกิดขึ้นพร้อมกันอย่างมาก
มาดูกันว่ามีการใช้งานร่วมกันอย่างไร
Public V Put (k key, v value) {เซ็กเมนต์ <k, v> s; if (value == null) โยน nullpointerexception ใหม่ (); int hash = hash (คีย์); int j = (แฮช >>> segmentshift) & segmentmask; if ((s = (เซ็กเมนต์ <k, v>) unsafe.getObject // nonvolatile; recheck (ส่วน, (j << sshift) + sbase)) == null) // ใน ensuresegment s = ensuresegment (j); return s.put (คีย์, แฮช, ค่า, เท็จ); -มีเซ็กเมนต์เซ็กเมนต์ภายในพร้อมกันพร้อมกันซึ่งแบ่งแฮชแมปขนาดใหญ่ออกเป็นหลายเซ็กเมนต์ (แฮชแมปขนาดเล็ก) จากนั้นแฮชข้อมูลในแต่ละเซ็กเมนต์ ด้วยวิธีนี้การดำเนินการแฮชของหลายเธรดในเซ็กเมนต์ต่าง ๆ จะต้องมีความปลอดภัยจากเธรดดังนั้นคุณจะต้องซิงโครไนซ์เธรดในเซ็กเมนต์เดียวกันซึ่งตระหนักถึงการแยกล็อคและเพิ่มการเกิดขึ้นพร้อมกันอย่างมาก
มันจะเป็นปัญหามากขึ้นเมื่อใช้พร้อมกัน chaShAshMap.Size เพราะจำเป็นต้องนับผลรวมข้อมูลของแต่ละเซ็กเมนต์ ในเวลานี้คุณต้องเพิ่มล็อคในแต่ละเซ็กเมนต์แล้วทำสถิติข้อมูล นี่เป็นข้อเสียเล็กน้อยหลังจากแยกล็อค แต่ไม่ควรเรียกใช้วิธีขนาดที่ความถี่สูง
ในแง่ของการใช้งานเราไม่ได้ใช้การซิงโครไนซ์และล็อคล็อค แต่ trylock ให้มากที่สุด ในเวลาเดียวกันเราได้ทำการปรับให้เหมาะสมในการดำเนินการของ HashMap ฉันจะไม่พูดถึงที่นี่
2.2 การปิดกั้น
BlockingQueue ไม่ใช่ภาชนะที่มีประสิทธิภาพสูง แต่มันเป็นคอนเทนเนอร์ที่ดีมากสำหรับการแบ่งปันข้อมูล เป็นการดำเนินการทั่วไปของผู้ผลิตและผู้บริโภค
แผนผังไดอะแกรม:
สำหรับรายละเอียดคุณสามารถตรวจสอบการใช้งาน Java ของผู้ผลิตและปัญหาผู้บริโภคและผู้อ่านและปัญหาผู้เขียน