ในฐานะเครื่องมือสำหรับการแบ่งปันข้อมูลพร้อมกันและรับประกันความสอดคล้อง การล็อคมีการใช้งานหลายอย่างบนแพลตฟอร์ม JAVA (เช่น การซิงโครไนซ์และ ReentrantLock เป็นต้น) ล็อคที่เขียนไว้แล้วเหล่านี้ให้ความสะดวกในการพัฒนาของเรา แต่ไม่ค่อยมีการกล่าวถึงลักษณะเฉพาะและประเภทของล็อค บทความชุดนี้จะวิเคราะห์ชื่อล็อคทั่วไปและคุณลักษณะภายใต้ JAVA เพื่อตอบคำถามของคุณ
1. ล็อคแบบหมุน
การล็อคการหมุนจะดำเนินการโดยการอนุญาตให้เธรดปัจจุบันดำเนินการอย่างต่อเนื่องภายในเนื้อหาของลูปเฉพาะเมื่อเงื่อนไขของลูปถูกเปลี่ยนแปลงโดยเธรดอื่นเท่านั้นที่สามารถป้อนส่วนวิกฤติได้ คัดลอกรหัสดังต่อไปนี้:
SpinLock คลาสสาธารณะ {
เครื่องหมาย AtomicReference <Thread> ส่วนตัว = AtomicReference ใหม่ <>();
โมฆะสาธารณะล็อค () {
เธรดปัจจุบัน = Thread.currentThread();
ในขณะที่(!sign .compareAndSet(null, ปัจจุบัน)){
-
-
การปลดล็อคโมฆะสาธารณะ (){
เธรดปัจจุบัน = Thread.currentThread();
ลงชื่อ .compareAndSet (ปัจจุบัน, null);
-
-
เมื่อใช้การดำเนินการแบบอะตอมมิกของ CAS ฟังก์ชันล็อคจะตั้งค่าเจ้าของเป็นเธรดปัจจุบัน และคาดการณ์ว่าค่าดั้งเดิมจะว่างเปล่า ฟังก์ชันปลดล็อคจะตั้งค่าเจ้าของเป็นโมฆะ และค่าที่คาดการณ์ไว้คือเธรดปัจจุบัน
เมื่อเธรดที่สองเรียกการดำเนินการล็อก เนื่องจากค่าเจ้าของไม่ว่างเปล่า การวนซ้ำจะถูกดำเนินการจนกว่าเธรดแรกเรียกใช้ฟังก์ชันปลดล็อกเพื่อตั้งค่าเจ้าของเป็น null และเธรดที่สองสามารถเข้าสู่ส่วนที่สำคัญได้
เนื่องจากสปินล็อคจะเก็บเฉพาะเธรดปัจจุบันที่ดำเนินการกับตัวลูปโดยไม่ต้องเปลี่ยนสถานะของเธรด ความเร็วในการตอบสนองจึงเร็วขึ้น แต่เมื่อจำนวนเธรดเพิ่มขึ้นอย่างต่อเนื่อง ประสิทธิภาพจะลดลงอย่างมาก เนื่องจากแต่ละเธรดจำเป็นต้องดำเนินการและกินเวลา CPU หากการแข่งขันของเธรดไม่รุนแรงและล็อคไว้เป็นระยะเวลาหนึ่ง เหมาะสำหรับใช้กับตัวล็อคแบบหมุน
หมายเหตุ: ตัวอย่างนี้เป็นการล็อคที่ไม่ยุติธรรม ลำดับการล็อคจะไม่ขึ้นอยู่กับลำดับการล็อค
2. ล็อคสปินแบบอื่นๆ
เราได้พูดคุยเกี่ยวกับ Spin Lock ข้างต้น มีรูปแบบการล็อคทั่วไปสามรูปแบบ: TicketLock, CLHlock และ MCSlock
การล็อคตั๋วช่วยแก้ปัญหาลำดับการเข้าถึงเป็นหลัก ปัญหาหลักอยู่ที่ CPU แบบมัลติคอร์:
คัดลอกรหัสรหัสดังต่อไปนี้:
แพ็คเกจ com.alipay.titan.dcc.dal.entity;
นำเข้า java.util.concurrent.atomic.AtomicInteger;
TicketLock ระดับสาธารณะ {
บริการ AtomicInteger ส่วนตัว = AtomicInteger ใหม่ ();
ส่วนตัว AtomicInteger ticketNum = AtomicInteger ใหม่ ();
ThreadLocal สุดท้ายแบบคงที่ส่วนตัว LOCAL = ใหม่ ThreadLocal <Integer> ();
ล็อคโมฆะสาธารณะ () {
int myticket = ticketNum.getAndIncreation();
LOCAL.set(มายทิคเก็ต);
ในขณะที่ (myticket != serviceNum.get()) {
-
-
โมฆะสาธารณะปลดล็อค () {
int myticket = LOCAL.get();
serviceNum.compareAndSet(myticket, myticket + 1);
-
-
ต้องสอบถามหมายเลขบริการ serviceNum ทุกครั้ง ซึ่งส่งผลต่อประสิทธิภาพ (ต้องอ่านจากหน่วยความจำหลัก และต้องป้องกันไม่ให้ CPU อื่นแก้ไข)
CLHLock และ MCSLock เป็นแฟร์ล็อคสองประเภทที่คล้ายกัน โดยจัดเรียงในรูปแบบของรายการที่เชื่อมโยง
คัดลอกรหัสรหัสดังต่อไปนี้:
นำเข้า java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
CLHLock คลาสสาธารณะ {
CLHNode คลาสคงที่สาธารณะ {
บูลีนระเหยส่วนตัว isLocked = true;
-
@SuppressWarnings("ไม่ได้ใช้")
หาง CLHNode ระเหยส่วนตัว;
ThreadLocal สุดท้ายแบบคงที่ส่วนตัว <CLHNode> LOCAL = ใหม่ ThreadLocal<CLHNode>();
AtomicReferenceFieldUpdater สุดท้ายแบบคงที่ส่วนตัว <CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater (CLHLock.class,
CLHNode.class, "ส่วนท้าย");
ล็อคโมฆะสาธารณะ () {
โหนด CLHNode = CLHNode ใหม่ ();
LOCAL.set(โหนด);
CLHNode preNode = UPDATER.getAndSet (นี่คือโหนด);
ถ้า (preNode != null) {
ในขณะที่ (preNode.isLocked) {
-
preNode = โมฆะ;
LOCAL.set(โหนด);
-
-
โมฆะสาธารณะปลดล็อค () {
โหนด CLHNode = LOCAL.get();
ถ้า (!UPDATER.compareAndSet(นี่, โหนด, null)) {
node.isLocked = เท็จ;
-
โหนด = โมฆะ;
-
-
CLHlock สืบค้นตัวแปรตั้งต้นอย่างต่อเนื่อง ทำให้ไม่เหมาะสมสำหรับการใช้งานภายใต้สถาปัตยกรรม NUMA (ในสถาปัตยกรรมนี้ แต่ละเธรดจะถูกกระจายในพื้นที่หน่วยความจำกายภาพที่แตกต่างกัน)
MCSLock วนซ้ำโหนดของตัวแปรภายในเครื่อง ไม่มีปัญหากับ CLHlock
คัดลอกรหัสรหัสดังต่อไปนี้:
นำเข้า java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
MCSLock คลาสสาธารณะ {
MCSNode คลาสคงที่สาธารณะ {
MCSNode ระเหยถัดไป;
บูลีนที่ระเหยได้ isLocked = true;
-
ThreadLocal สุดท้ายแบบคงที่ส่วนตัว <MCSNode> NODE = ThreadLocal ใหม่ <MCSNode>();
@SuppressWarnings("ไม่ได้ใช้")
คิว MCSNode ระเหยส่วนตัว
AtomicReferenceFieldUpdater <MCSLock, MCSNode> UPDATER สุดท้ายแบบคงที่ส่วนตัว = AtomicReferenceFieldUpdater.newUpdater (MCSLock.class,
MCSNode.class, "คิว");
ล็อคโมฆะสาธารณะ () {
MCSNode currentNode = MCSNode ใหม่();
NODE.set(โหนดปัจจุบัน);
MCSNode preNode = UPDATER.getAndSet (นี่คือ currentNode);
ถ้า (preNode != null) {
preNode.next = ปัจจุบันโหนด;
ในขณะที่ (currentNode.isLocked) {
-
-
-
โมฆะสาธารณะปลดล็อค () {
MCSNode currentNode = NODE.get();
ถ้า (currentNode.next == null) {
ถ้า (UPDATER.compareAndSet (นี้, currentNode, null)) {
} อื่น {
ในขณะที่ (currentNode.next == null) {
-
-
} อื่น {
currentNode.next.isLocked = เท็จ;
currentNode.next = โมฆะ;
-
-
-
จากมุมมองของโค้ด CLH นั้นง่ายกว่า MCS
คิว CLH เป็นคิวโดยนัยและไม่มีแอ็ตทริบิวต์โหนดที่สืบทอดจริง
คิว MCS เป็นคิวที่ชัดเจนซึ่งมีแอตทริบิวต์โหนดที่สืบทอดจริง
การล็อคเริ่มต้นที่ใช้ภายในโดย JUC ReentrantLock คือการล็อค CLH (มีการปรับปรุงมากมาย เช่น การเปลี่ยนการล็อคแบบหมุนด้วยการล็อคการบล็อค เป็นต้น)
(จบข้อความฉบับเต็ม)