สปินล็อคคืออะไร
เมื่อพูดถึงการล็อคสปินเราต้องเริ่มต้นด้วยกลไกการล็อคภายใต้มัลติเธรด เนื่องจากทรัพยากรบางอย่างในสภาพแวดล้อมระบบหลายโปรเซสเซอร์มี จำกัด บางครั้งพวกเขาจึงต้องการการยกเว้นซึ่งกันและกัน ในเวลานี้จะมีการแนะนำกลไกการล็อค เฉพาะกระบวนการที่ได้รับล็อคเท่านั้นที่สามารถเข้าถึงทรัพยากรได้ นั่นคือกระบวนการเดียวเท่านั้นที่สามารถรับล็อคได้ครั้งละเพื่อเข้าสู่พื้นที่สำคัญของตนเอง ในเวลาเดียวกันกระบวนการสองกระบวนการขึ้นไปไม่สามารถเข้าสู่พื้นที่วิกฤตได้ เมื่อออกจากพื้นที่วิกฤตล็อคจะถูกปล่อยออกมา
เมื่อออกแบบอัลกอริทึม Mutex คุณจะต้องเผชิญกับสถานการณ์ที่คุณไม่มีล็อคอยู่เสมอนั่นคือคุณควรทำอย่างไรถ้าคุณไม่ได้ล็อค
โดยปกติจะมี 2 วิธีในการจัดการกับมัน:
หนึ่งคือผู้โทรที่ไม่ได้รับการล็อคกำลังวนอยู่รอบ ๆ เพื่อดูว่าผู้ถือสปินล็อคได้ปล่อยล็อคหรือไม่ นี่คือจุดสนใจของบทความนี้ - สปินล็อค เขาไม่จำเป็นต้องปิดกั้นเมืองสาย (ไม่ปิดกั้น)
อีกวิธีหนึ่งคือกระบวนการที่ไม่ได้รับการล็อคบล็อกตัวเอง (การบล็อก) และยังคงดำเนินงานอื่น ๆ ในเธรดซึ่งเป็น mutex (รวมถึงการล็อคในตัวที่ซิงโครไนซ์, reentrantlock ฯลฯ )
การแนะนำ
CAS (เปรียบเทียบและแลกเปลี่ยน) นั่นคือการเปรียบเทียบและการแลกเปลี่ยนยังเป็นการดำเนินการหลักที่ใช้สิ่งที่เรามักเรียกว่าสปินล็อคหรือการล็อคในแง่ดี
การใช้งานนั้นง่ายมากซึ่งคือการเปรียบเทียบค่าที่คาดหวังกับค่าหน่วยความจำ หากค่าทั้งสองเท่ากันให้แทนที่ค่าหน่วยความจำด้วยค่าที่คาดหวังและส่งคืนจริง มิฉะนั้นให้ส่งคืนเท็จ
ตรวจสอบให้แน่ใจว่าการทำงานของอะตอม
เทคโนโลยีใด ๆ ที่เกิดขึ้นเพื่อแก้ปัญหาเฉพาะบางอย่าง ปัญหาที่ CAS จำเป็นต้องแก้ไขคือเพื่อให้แน่ใจว่าการดำเนินการของอะตอม การผ่าตัดอะตอมคืออะไร? อะตอมนั้นเล็กที่สุดและไม่เหมาะสมและการทำงานของอะตอมเป็นการดำเนินการที่เล็กที่สุดและไม่เหมาะสม กล่าวคือเมื่อการดำเนินการเริ่มต้นจะไม่สามารถขัดจังหวะและรู้ว่าการดำเนินการเสร็จสมบูรณ์ ในสภาพแวดล้อมแบบมัลติเธรดการดำเนินการอะตอมเป็นวิธีสำคัญในการรับรองความปลอดภัยของด้าย ตัวอย่างเช่นสมมติว่ามีสองเธรดที่ทำงานและต้องการแก้ไขค่าที่แน่นอน ใช้การดำเนินการในตัวเองเป็นตัวอย่าง ในการดำเนินการในตัวเองบนจำนวนเต็ม I จำเป็นต้องมีขั้นตอนพื้นฐานสามขั้นตอน:
1. อ่านค่าปัจจุบันของ i;
2. เพิ่ม 1 ถึงค่า I;
3. เขียนค่า I กลับไปที่หน่วยความจำ;
สมมติว่ากระบวนการทั้งสองอ่านค่าปัจจุบันของ I โดยสมมติว่าเป็น 0 ในเวลานี้เธรด A เพิ่ม 1 ถึง I, Thread B ยังเพิ่ม 1 และในที่สุดฉันก็คือ 1 ไม่ใช่ 2 นี่เป็นเพราะการดำเนินการอัตโนมัติไม่ใช่การดำเนินการอะตอมและสามขั้นตอนที่แบ่งออกเป็น ดังในตัวอย่างด้านล่างสำหรับ 10 เธรดแต่ละเธรดดำเนินการ 10,000 I ++ ค่าที่คาดหวังคือ 100,000 แต่น่าเสียดายที่ผลลัพธ์จะน้อยกว่า 100,000 เสมอ
int คงที่ i = 0; โมฆะคงที่สาธารณะเพิ่ม () {i ++; } คลาสสแตติกส่วนตัว Plus ใช้งานได้ {@Override public void run () {สำหรับ (int k = 0; k <10,00000; k ++) {เพิ่ม (); }}} โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่น InterruptedException {เธรด [] เธรด = เธรดใหม่ [10]; สำหรับ (int i = 0; i <10; i ++) {เธรด [i] = เธรดใหม่ (ใหม่บวก ()); เธรด [i]. start (); } สำหรับ (int i = 0; i <10; i ++) {เธรด [i] .oin (); } system.out.println (i); -ในกรณีนี้ฉันควรทำอย่างไร? ถูกต้องบางทีคุณอาจคิดอยู่แล้วคุณสามารถล็อคหรือใช้การใช้งานที่ซิงโครไนซ์ตัวอย่างเช่นแก้ไขวิธีการเพิ่ม () เป็นสิ่งต่อไปนี้:
โมฆะคงที่แบบซิงโครไนซ์สาธารณะเพิ่ม () {i ++; -อีกวิธีหนึ่งการดำเนินการล็อคจะถูกนำมาใช้ตัวอย่างเช่นโดยใช้ reentrantlock (reentrantlock)
ล็อคแบบคงที่ส่วนตัว = ใหม่ reentrantlock (); โมฆะคงที่สาธารณะเพิ่ม () {lock.lock (); i ++; lock.unlock (); - CAS ใช้สปินล็อค
เนื่องจากการดำเนินการอะตอมสามารถใช้งานได้โดยใช้คำหลักล็อคหรือซิงโครไนซ์ทำไมต้องใช้ CAS เนื่องจากการล็อคหรือการใช้คำหลักที่ซิงโครไนซ์ทำให้เกิดการสูญเสียประสิทธิภาพจำนวนมากในขณะที่ใช้ CAS สามารถบรรลุการล็อคในแง่ดี จริง ๆ แล้วมันใช้คำแนะนำระดับ CPU โดยตรงดังนั้นประสิทธิภาพจึงสูงมาก
ดังที่ได้กล่าวไว้ข้างต้น CAS เป็นพื้นฐานสำหรับการใช้ล็อคสปิน CAS ใช้คำแนะนำ CPU เพื่อให้แน่ใจว่าอะตอมของการดำเนินการเพื่อให้ได้เอฟเฟกต์ล็อค สำหรับการหมุนมันก็ชัดเจนมากที่จะอ่านความหมายที่แท้จริง ถ้าคุณหมุนตัวเองมันเป็นวง โดยทั่วไปจะถูกนำไปใช้โดยใช้ลูปที่ไม่มีที่สิ้นสุด ด้วยวิธีนี้การดำเนินการ CAS จะดำเนินการในวงไม่มีที่สิ้นสุด เมื่อการดำเนินการประสบความสำเร็จและส่งคืนจริงลูปจะสิ้นสุดลง เมื่อเท็จลูปจะถูกดำเนินการและการดำเนินการ CAS จะดำเนินต่อไปจนกว่าจะถูกส่งคืนจริง
ในความเป็นจริงสถานที่หลายแห่งใน JDK ใช้ CAS โดยเฉพาะอย่างยิ่งในแพ็คเกจ Java.util.Concurrent เช่น Countdownlatch, Semaphore, Reentrantlock และ Java.util.concurrent.atomic แพ็คเกจ ฉันเชื่อว่าทุกคนใช้อะตอม*เช่น Atomicboolean, Atomicinteger ฯลฯ
ที่นี่เราใช้ตัวอย่าง Atomicboolean เพราะมันง่ายพอ
Atomicboolean ชั้นเรียนสาธารณะใช้ java.io.serializable {ส่วนตัวคงที่สุดท้าย Long SerialVersionuid = 4654671469794556979L; // การตั้งค่าเพื่อใช้ unsafe.compareandswapint สำหรับการอัปเดตส่วนตัวคงที่ไม่ปลอดภัยไม่ปลอดภัย = unsafe.getUnsafe (); ส่วนตัวคงที่ครั้งสุดท้าย Long ValueOffset; คงที่ {ลอง {valueOffSet = unsafe.ObjectFieldOffset (AtomicBoolean.class.getDeclaredField ("value")); } catch (exception ex) {โยนข้อผิดพลาดใหม่ (ex); }} ค่า int ผันผวนส่วนตัว; บูลีนสุดท้ายสาธารณะรับ () {ค่าคืน! = 0; } Public Final Boolean เปรียบเทียบ (คาดหวังบูลีน, การอัปเดตบูลีน) {int e = คาดหวัง? 1: 0; int u = อัปเดต? 1: 0; return unsafe.compareandswapint (นี่, valueOffset, e, u); -นี่เป็นส่วนหนึ่งของรหัสของ Atomicboolean และเราเห็นวิธีการและคุณสมบัติที่สำคัญหลายประการที่นี่
1. ใช้วัตถุ Sun.misc.unsafe คลาสนี้มีชุดของวิธีการในการใช้งานวัตถุหน่วยความจำโดยตรง แต่ใช้เฉพาะภายในโดย JDK และไม่แนะนำให้นักพัฒนาใช้
2. ค่าแสดงถึงค่าที่แท้จริง คุณจะเห็นว่าวิธีการรับจริงตัดสินค่าบูลีนขึ้นอยู่กับว่าค่าเท่ากับ 0 ค่าที่นี่ถูกกำหนดเป็นความผันผวนหรือไม่เนื่องจากความผันผวนสามารถมั่นใจได้ว่าการมองเห็นหน่วยความจำนั้นคือตราบใดที่ค่าเปลี่ยนค่า บทความถัดไปจะพูดคุยเกี่ยวกับการมองเห็นความผันผวนยินดีต้อนรับสู่การติดตาม
3. ValueOffset เป็นหน่วยความจำออฟเซ็ตของค่าที่ได้รับโดยใช้วิธี unsafe.ObjectFieldOffset และใช้เป็นวิธีการเปรียบเทียบที่ตามมา
4. วิธีการเปรียบเทียบนี้เป็นวิธีการหลักในการใช้ CAS เมื่อใช้วิธี Atomicboolean คุณจะต้องผ่านค่าที่คาดหวังและค่าที่จะอัปเดต เรียกว่า unsafe.compareandswapint (สิ่งนี้, ValueOffset, E, U) วิธีการ มันเป็นวิธีการดั้งเดิมที่ใช้ใน C ++ และรหัสเฉพาะจะไม่ถูกโพสต์ ในระยะสั้นมันใช้คำสั่ง CMMPXCHG ของ CPU เพื่อทำการเปรียบเทียบและเปลี่ยน แน่นอนขึ้นอยู่กับรุ่นระบบเฉพาะนอกจากนี้ยังมีความแตกต่างในการใช้งาน ผู้ที่สนใจสามารถค้นหาบทความที่เกี่ยวข้องด้วยตัวเอง
ใช้สถานการณ์
ตัวอย่างเช่น Atomicboolean สามารถใช้ในสถานการณ์ดังกล่าว ระบบจำเป็นต้องพิจารณาว่าการดำเนินการเริ่มต้นบางอย่างจำเป็นต้องดำเนินการตามคุณสมบัติของสถานะของตัวแปรบูลีนหรือไม่ หากเป็นสภาพแวดล้อมแบบมัลติเธรดและหลีกเลี่ยงการดำเนินการซ้ำ ๆ ก็สามารถนำไปใช้โดยใช้ Atomicboolean pseudocode มีดังนี้:
Atomicboolean Flag คงสุดท้ายส่วนตัว = New Atomicboolean (); if (flag.CompareAndSet (เท็จ, จริง)) {init (); -ตัวอย่างเช่น Atomicinteger สามารถใช้ในเคาน์เตอร์และในสภาพแวดล้อมแบบมัลติเธรดเพื่อให้แน่ใจว่าการนับแม่นยำ
คำถาม ABA
มีปัญหากับ CAS ซึ่งเป็นค่าที่เปลี่ยนแปลงจาก A เป็น B จากนั้นจาก B เป็น A ในกรณีนี้ CAS จะคิดว่าค่าไม่เปลี่ยนแปลง แต่ในความเป็นจริงมันเปลี่ยนไป ในเรื่องนี้มี atomicstampedreference ภายใต้แพ็คเก็ตพร้อมกันที่ให้การใช้งานตามหมายเลขเวอร์ชันซึ่งสามารถแก้ปัญหาบางอย่างได้
สรุป
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่าเนื้อหาของบทความนี้จะมีค่าอ้างอิงบางอย่างสำหรับการศึกษาหรือที่ทำงานของทุกคน หากคุณมีคำถามใด ๆ คุณสามารถฝากข้อความไว้เพื่อสื่อสาร ขอบคุณสำหรับการสนับสนุน Wulin.com