CodeGym /หลักสูตรจาวา /โมดูล 3 /ปฏิบัติการปรมาณูในภาษาจาวา

ปฏิบัติการปรมาณูในภาษาจาวา

โมดูล 3
ระดับ , บทเรียน
มีอยู่

ข้อกำหนดเบื้องต้นสำหรับการเกิดขึ้นของการดำเนินการปรมาณู

มาดูตัวอย่างนี้เพื่อช่วยให้คุณเข้าใจการทำงานของอะตอม:

public class Counter {
    int count;

    public void increment() {
        count++;
    }
}

เมื่อเรามีหนึ่งเธรด ทุกอย่างทำงานได้ดี แต่ถ้าเราเพิ่มมัลติเธรด เราจะได้ผลลัพธ์ที่ผิด และทั้งหมดเป็นเพราะการดำเนินการเพิ่มไม่ใช่การดำเนินการเดียว แต่เป็นสาม: คำขอเพื่อรับค่าปัจจุบันนับจากนั้นเพิ่มทีละ 1 แล้วเขียนอีกครั้งเป็นนับ.

และเมื่อสองเธรดต้องการเพิ่มตัวแปร คุณมักจะสูญเสียข้อมูล นั่นคือทั้งสองเธรดได้รับ 100 ดังนั้นทั้งคู่จะเขียน 101 แทนค่าที่คาดไว้ 102

แล้วจะแก้ยังไง? คุณต้องใช้ล็อค คีย์เวิร์ดที่ซิงโครไนซ์ช่วยแก้ปัญหานี้ โดยใช้คีย์เวิร์ดนี้รับประกันว่าเธรดหนึ่งรายการจะเข้าถึงเมธอดในแต่ละครั้ง

public class SynchronizedCounterWithLock {
    private volatile int count;

    public synchronized void increment() {
        count++;
    }
}

นอกจากนี้ คุณต้องเพิ่ม คำหลักที่ผันผวนซึ่งช่วยให้มองเห็นการอ้างอิงระหว่างเธรดต่างๆ ได้อย่างถูกต้อง เราได้ตรวจสอบงานของเขาด้านบน

แต่ก็ยังมีข้อเสีย สิ่งที่ใหญ่ที่สุดคือประสิทธิภาพ ณ เวลาที่หลายเธรดพยายามรับการล็อกและมีโอกาสเขียน เธรดที่เหลือจะถูกบล็อกหรือระงับจนกว่าเธรดจะถูกปล่อย

กระบวนการทั้งหมดเหล่านี้ การบล็อก การเปลี่ยนไปใช้สถานะอื่นมีราคาแพงมากสำหรับประสิทธิภาพของระบบ

ปฏิบัติการปรมาณู

อัลกอริทึมใช้คำสั่งเครื่องจักรระดับต่ำ เช่น การเปรียบเทียบและการแลกเปลี่ยน (CAS, การเปรียบเทียบและการแลกเปลี่ยน ซึ่งรับประกันความสมบูรณ์ของข้อมูลและมีการวิจัยจำนวนมากอยู่แล้ว)

การดำเนินการ CAS ทั่วไปดำเนินการกับตัวถูกดำเนินการสามตัว:

  • พื้นที่หน่วยความจำสำหรับการทำงาน (M)
  • ค่าคาดหวังที่มีอยู่ (A) ของตัวแปร
  • ค่าใหม่ (B) ที่จะตั้งค่า

CAS จะอัปเดตอะตอมของ M เป็น B แต่ถ้าค่าของ M เหมือนกับ A เท่านั้น มิฉะนั้นจะไม่ดำเนินการใดๆ

ในกรณีแรกและกรณีที่สอง ค่าของ M จะถูกส่งกลับ วิธีนี้ทำให้คุณสามารถรวมสามขั้นตอน ได้แก่ การรับค่า การเปรียบเทียบค่า และการอัปเดต และทั้งหมดจะกลายเป็นการดำเนินการเดียวที่ระดับเครื่องจักร

ทันทีที่แอปพลิเคชันแบบมัลติเธรดเข้าถึงตัวแปรและพยายามอัปเดตและใช้ CAS เธรดตัวใดตัวหนึ่งจะได้รับและสามารถอัปเดตได้ แต่แตกต่างจากการล็อก เธรดอื่นๆ จะได้รับข้อผิดพลาดเกี่ยวกับการไม่สามารถอัปเดตค่าได้ จากนั้นพวกเขาจะไปทำงานต่อไปและการสลับจะไม่รวมอยู่ในงานประเภทนี้โดยสิ้นเชิง

ในกรณีนี้ ตรรกะจะยากขึ้นเนื่องจากเราต้องจัดการกับสถานการณ์เมื่อการดำเนินการ CAS ทำงานไม่สำเร็จ เราจะจำลองโค้ดเพื่อไม่ให้ดำเนินการต่อจนกว่าการดำเนินการจะสำเร็จ

ความรู้เบื้องต้นเกี่ยวกับประเภทของอะตอม

คุณเคยเจอสถานการณ์ที่ต้องตั้งค่าการซิงโครไนซ์สำหรับตัวแปรประเภทที่ง่ายที่สุดintหรือไม่

วิธีแรกที่เรากล่าวถึงคือการใช้volatile + synchronized แต่ยังมีคลาส Atomic* พิเศษอีกด้วย

หากเราใช้ CAS การดำเนินการจะทำงานได้เร็วกว่าเมื่อเทียบกับวิธีแรก นอกจากนี้ เรายังมีวิธีการพิเศษและสะดวกมากสำหรับการเพิ่มมูลค่าและการดำเนินการเพิ่มและลด

AtomicBoolean , AtomicInteger , AtomicLong , AtomicIntegerArray , AtomicLongArrayเป็นคลาสที่มีการดำเนินการเป็นอะตอมมิก ด้านล่างเราจะวิเคราะห์งานกับพวกเขา

จำนวนเต็มอะตอม

คลาสAtomicIntegerให้การดำเนินการกับ ค่า intที่สามารถอ่านและเขียนได้ทางอะตอม นอกเหนือจากการดำเนินการทางอะตอมแบบขยาย

มีเมธอดรับและเซ็ ต ที่ทำงานเหมือนกับการอ่านและเขียนตัวแปร

นั่นคือ "เกิดขึ้นก่อน" ด้วยการรับตัวแปรเดียวกันที่เราพูดถึงก่อนหน้านี้ในภายหลัง วิธีการเปรียบเทียบ อะตอมและการตั้งค่า ยังมีคุณสมบัติความสอดคล้องของหน่วยความจำเหล่านี้ด้วย

การดำเนินการทั้งหมดที่ส่งคืนค่าใหม่จะดำเนินการแบบอะตอม:

int addAndGet (int เดลต้า) เพิ่มค่าเฉพาะให้กับค่าปัจจุบัน
บูลีนเปรียบเทียบ AndSet (คาดว่า int, อัปเดต int) ตั้งค่าเป็นค่าที่อัปเดตแล้วหากค่าปัจจุบันตรงกับค่าที่คาดไว้
int ลดลงและรับ () ลดค่าปัจจุบันทีละหนึ่ง
int getAndAdd (int เดลต้า) เพิ่มค่าที่กำหนดให้กับค่าปัจจุบัน
int getAndDecrement() ลดค่าปัจจุบันทีละหนึ่ง
int getAndIncrement() เพิ่มค่าปัจจุบันทีละหนึ่ง
int getAndSet(int ค่าใหม่) ตั้งค่าที่กำหนดและส่งกลับค่าเก่า
int เพิ่มขึ้นและรับ () เพิ่มค่าปัจจุบันทีละหนึ่ง
lazySet (int ค่าใหม่) สุดท้ายกำหนดเป็นค่าที่กำหนด
บูลีนจุดอ่อนCompareAndSet (คาดว่า, อัปเดต int) ตั้งค่าเป็นค่าที่อัปเดตแล้วหากค่าปัจจุบันตรงกับค่าที่คาดไว้

ตัวอย่าง:

ExecutorService executor = Executors.newFixedThreadPool(5);
IntStream.range(0, 50).forEach(i -> executor.submit(atomicInteger::incrementAndGet));
executor.shutdown();
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.HOURS);

System.out.println(atomicInteger.get()); // prints 50
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION