เหตุใดจึงต้องใช้ ReentrantLock ถ้าใครสามารถใช้ข้อมูลให้ตรงกัน (นี่)


317

synchronized (this)ฉันพยายามที่จะเข้าใจสิ่งที่ทำให้ล็อคในการทำงานพร้อมกันที่สำคัญดังนั้นหากสามารถใช้ ในรหัสจำลองด้านล่างฉันสามารถทำอย่างใดอย่างหนึ่ง:

  1. ทำข้อมูลให้ตรงกันทั้งวิธีหรือประสานพื้นที่เสี่ยง ( synchronized(this){...})
  2. หรือล็อคพื้นที่โค้ดที่มีช่องโหว่ด้วย ReentrantLock

รหัส:

    private final ReentrantLock lock = new ReentrantLock(); 
    private static List<Integer> ints;

    public Integer getResult(String name) { 
        .
        .
        .
        lock.lock();
        try {
            if (ints.size()==3) {
                ints=null;
                return -9;
            }   

            for (int x=0; x<ints.size(); x++) {
                System.out.println("["+name+"] "+x+"/"+ints.size()+". values >>>>"+ints.get(x));
            }

        } finally {
            lock.unlock();
        } 
        return random;
}

1
BTW การล็อกที่แท้จริงของจาวาทั้งหมดเป็น reentrant โดยธรรมชาติ
Aniket Thakur

@pongapundit ดังนั้นsynchronized(this){synchronized(this){//some code}}จะไม่ทำให้เกิดการล็อคตาย สำหรับการล็อคที่แท้จริงถ้าพวกเขาได้รับการตรวจสอบทรัพยากรและหากพวกเขาต้องการมันอีกครั้งพวกเขาสามารถได้รับมันโดยไม่ต้องล็อคตาย
Aniket Thakur

คำตอบ:


474

ReentrantLockเป็นที่ไม่มีโครงสร้างซึ่งแตกต่างจากsynchronizedโครงสร้าง - คือคุณไม่จำเป็นต้องใช้โครงสร้างบล็อกสำหรับล็อคและยังสามารถถือล็อคทั่ววิธี ตัวอย่าง:

private ReentrantLock lock;

public void foo() {
  ...
  lock.lock();
  ...
}

public void bar() {
  ...
  lock.unlock();
  ...
}

การไหลดังกล่าวเป็นไปไม่ได้ที่จะแสดงผ่านจอภาพเดียวในsynchronizedโครงสร้าง


นอกเหนือจากที่ReentrantLockสนับสนุนล็อคเลือกตั้งและล็อค Interruptible รอว่าการสนับสนุนการหมดเวลา ReentrantLockนอกจากนี้ยังมีการสนับสนุนสำหรับนโยบายความเป็นธรรมที่สามารถกำหนดค่าได้ช่วยให้การกำหนดตารางเวลาเธรดมีความยืดหยุ่นมากขึ้น

ตัวสร้างสำหรับคลาสนี้ยอมรับพารามิเตอร์fairness ที่เป็นทางเลือก เมื่อตั้งค่าtrueภายใต้การช่วงชิงล็อคจะอนุญาตให้เข้าถึงเธรดที่รอคอยนานที่สุด มิฉะนั้นการล็อคนี้จะไม่รับประกันการเข้าถึงคำสั่งใด ๆ โปรแกรมที่ใช้การล็อกที่ยุติธรรมที่เข้าถึงได้โดยเธรดจำนวนมากอาจแสดงปริมาณงานโดยรวมที่ต่ำกว่า (เช่นช้ากว่ามักช้ากว่า) ที่ใช้การตั้งค่าเริ่มต้น แต่มีความแปรปรวนน้อยกว่า อย่างไรก็ตามโปรดทราบว่าความเป็นธรรมของการล็อคไม่รับประกันความเป็นธรรมของการจัดตารางเวลาเธรด ดังนั้นหนึ่งในหลาย ๆ เธรดที่ใช้การล็อคแบบยุติธรรมอาจได้รับหลาย ๆ ครั้งติดต่อกันในขณะที่เธรดที่ใช้งานอยู่อื่น ๆ ไม่คืบหน้าและไม่ได้ล็อคการล็อคไว้ ยังทราบว่า untimedtryLockวิธีการไม่ให้เกียรติการตั้งค่าความเป็นธรรม มันจะประสบความสำเร็จถ้าล็อคพร้อมใช้งานแม้ว่าเธรดอื่นกำลังรออยู่


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

อย่างไรก็ตามการอ้างสิทธิ์นี้ถูกโต้แย้ง ดูความคิดเห็นต่อไปนี้:

ในการทดสอบการล็อก reentrant การล็อกใหม่จะถูกสร้างขึ้นในแต่ละครั้งดังนั้นจึงไม่มีการล็อกแบบเอกสิทธิ์เฉพาะบุคคลและข้อมูลที่ได้จะไม่ถูกต้อง นอกจากนี้ลิงก์ IBM ยังไม่มีซอร์สโค้ดสำหรับเกณฑ์มาตรฐานดังนั้นจึงเป็นไปไม่ได้ที่จะระบุว่าการทดสอบนั้นดำเนินการอย่างถูกต้องหรือไม่


เมื่อคุณควรใช้ReentrantLockหรือไม่? ตามบทความ developerWorks นั้น ...

คำตอบนั้นง่ายมาก - ใช้เมื่อคุณต้องการบางสิ่งที่มันจัดหาให้ซึ่งsynchronizedไม่ได้เช่นล็อคหมดเวลารอล็อคขัดจังหวะรอล็อคไม่ล็อคโครงสร้างโครงสร้างตัวแปรเงื่อนไขหลายหรือโพลล์ล็อค ReentrantLockยังมีประโยชน์ในการปรับขยายได้และคุณควรใช้มันหากคุณมีสถานการณ์ที่แสดงถึงความขัดแย้งสูง แต่จำไว้ว่าsynchronizedบล็อกส่วนใหญ่แทบจะไม่เคยแสดงความขัดแย้งใด ๆ เลย ฉันจะแนะนำให้พัฒนาด้วยการซิงโครไนซ์จนกว่าการซิงโครไนซ์จะได้รับการพิสูจน์แล้วว่าไม่เพียงพอแทนที่จะเพียงแค่สมมติว่า "ประสิทธิภาพจะดีขึ้น" ถ้าคุณใช้ReentrantLock. จำไว้ว่านี่เป็นเครื่องมือขั้นสูงสำหรับผู้ใช้ขั้นสูง (และผู้ใช้ขั้นสูงอย่างแท้จริงมักจะชอบเครื่องมือที่ง่ายที่สุดที่พวกเขาสามารถหาได้จนกว่าพวกเขาจะมั่นใจว่าเครื่องมือง่าย ๆ นั้นไม่เพียงพอ) เช่นเคยทำให้มันถูกต้องก่อนแล้วจึงกังวลว่าคุณจะต้องทำให้เร็วขึ้นหรือไม่


26
ควรลบลิงก์ 'ที่รู้จักกันว่าสามารถปรับขนาดได้มากกว่าไปยัง lycog.com ในการทดสอบการล็อก reentrant การล็อกใหม่จะถูกสร้างขึ้นในแต่ละครั้งดังนั้นจึงไม่มีการล็อกแบบเอกสิทธิ์เฉพาะบุคคลและข้อมูลที่ได้จะไม่ถูกต้อง นอกจากนี้ลิงก์ IBM ยังไม่มีซอร์สโค้ดสำหรับเกณฑ์มาตรฐานดังนั้นจึงเป็นไปไม่ได้ที่จะระบุว่าการทดสอบนั้นดำเนินการอย่างถูกต้องหรือไม่ โดยส่วนตัวแล้วฉันจะลบทั้งบรรทัดเกี่ยวกับความสามารถในการปรับขนาดได้เนื่องจากการอ้างสิทธิ์ทั้งหมดไม่ได้รับการสนับสนุน
Dev

2
ฉันแก้ไขโพสต์ตามการตอบสนองของคุณ
oldrinb

6
หากประสิทธิภาพเป็นสิ่งที่คุณให้ความสำคัญอย่าลืมหาวิธีที่คุณไม่จำเป็นต้องซิงโครไนซ์เลย
mcoolive

2
สิ่งที่แสดงไม่สมเหตุสมผลสำหรับฉันเลย หาก reantrant lock จะทำงานได้ดีขึ้นแล้วทำไมจะไม่ซิงโครไนซ์ไม่เพียง แต่นำไปใช้ในลักษณะเดียวกับ reantrant lock interatlly?
tObi

2
@ user2761895 ReentrantLockPseudoRandomรหัสในลิงก์ Lycog นั้นใช้การล็อกใหม่และไม่มีการควบคุมทุกคำขอร้องsetSeedและnext
oldrinb

14

ReentrantReadWriteLockเป็นล็อคแบบพิเศษในขณะที่synchronized(this)เป็นล็อควัตถุประสงค์ทั่วไป พวกมันคล้ายกัน แต่ก็ไม่เหมือนกัน

คุณพูดถูกที่คุณสามารถใช้synchronized(this)แทนได้ReentrantReadWriteLockแต่สิ่งที่ตรงกันข้ามนั้นไม่จริงเสมอไป

หากคุณต้องการเข้าใจสิ่งที่ทำให้ReentrantReadWriteLockพิเศษค้นหาข้อมูลเกี่ยวกับการซิงโครไนซ์เธรดผู้ผลิต - ผู้บริโภค

โดยทั่วไปคุณสามารถจำได้ว่าการซิงโครไนซ์ทั้งวิธีและการซิงโครไนซ์เพื่อวัตถุประสงค์ทั่วไป (โดยใช้synchronizedคำหลัก) สามารถใช้ในแอปพลิเคชันส่วนใหญ่โดยไม่ต้องคิดถึงความหมายของการซิงโครไนซ์มากเกินไปแต่ถ้าคุณต้องการบีบประสิทธิภาพออกจากโค้ดของคุณ สำรวจกลไกการซิงโครไนซ์ที่ละเอียดยิ่งขึ้นหรือวัตถุประสงค์พิเศษอื่น ๆ เพิ่มเติม

อย่างไรก็ตามการใช้synchronized(this)- และการล็อกทั่วไปโดยใช้อินสแตนซ์คลาสสาธารณะ - อาจเป็นปัญหาได้เนื่องจากมันเปิดรหัสของคุณไปยังล็อกที่อาจเกิดขึ้นได้เนื่องจากคนอื่นที่ไม่รู้ตัวอาจพยายามล็อควัตถุของคุณที่อื่นในโปรแกรม


เพื่อป้องกันการล็อคตายที่อาจเกิดขึ้นเพราะคนอื่นไม่ได้รู้เท่าทันอาจพยายามล็อควัตถุของคุณที่อื่นในเกมใช้อินสแตนซ์วัตถุส่วนตัวเป็นการซิงโครไนซ์มอนิเตอร์เช่นนี้: public class MyLock { private final Object protectedLongLockingMonitor = new Object(); private long protectedLong = 0L; public void incrementProtectedLong() { synchronized(protectedLongLockingMonitor) { protectedLong++; } } }
sushicutta

9

จากหน้าเอกสาร oracle เกี่ยวกับReentrantLock :

Reentrant mutual exclusion Lock ที่มีพฤติกรรมพื้นฐานและความหมายเหมือนกับการล็อกการมอนิเตอร์โดยนัยที่เข้าถึงได้โดยใช้เมธอดและข้อความสั่งที่ซิงโครไนซ์ แต่มีความสามารถเพิ่มเติม

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

  2. ตัวสร้างสำหรับคลาสนี้ยอมรับพารามิเตอร์fairness ที่เป็นทางเลือก เมื่อตั้งค่าที่แท้จริงภายใต้การต่อสู้ ล็อคกรุณาอนุญาตให้เข้าถึงด้ายยาวที่สุดการรอคอย มิฉะนั้นการล็อคนี้จะไม่รับประกันการเข้าถึงคำสั่งใด ๆ

คุณสมบัติที่สำคัญของReentrantLockเป็นไปตามบทความนี้

  1. ความสามารถในการล็อคอินเตอร์รัปต์
  2. ความสามารถในการหมดเวลาในขณะที่รอการล็อค
  3. พลังในการสร้างการล็อคที่ยุติธรรม
  4. API เพื่อรับรายการเธรดที่รอการล็อก
  5. ความยืดหยุ่นในการลองล็อคโดยไม่ปิดกั้น

คุณสามารถใช้ ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLockเพื่อรับการควบคุมเพิ่มเติมเกี่ยวกับการล็อคแบบละเอียดในการอ่านและเขียน

ดูบทความนี้โดย Benjamen เกี่ยวกับการใช้ReentrantLocksประเภทต่างๆ


2

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

private final ReentrantLock lock = new ReentrantLock(true);
//the param true turns on the fairness policy. 

"นโยบายความเป็นธรรม" เลือกเธรดที่รันได้ถัดไปเพื่อดำเนินการ มันขึ้นอยู่กับลำดับความสำคัญเวลาตั้งแต่การเรียกใช้ครั้งล่าสุด blah blah

การซิงโครไนซ์สามารถบล็อกได้ไม่ จำกัด หากไม่สามารถหลีกเลี่ยงบล็อกได้ Reentrantlock สามารถตั้งค่าการหมดเวลาได้


1

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

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


1

สมมติว่าโค้ดนี้กำลังทำงานในเธรด:

private static ReentrantLock lock = new ReentrantLock();

void accessResource() {
    lock.lock();
    if( checkSomeCondition() ) {
        accessResource();
    }
    lock.unlock();
}

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


0

สิ่งหนึ่งที่ต้องคำนึงถึงคือ

ชื่อ ' ReentrantLock ' ให้ข้อความที่ไม่ถูกต้องเกี่ยวกับกลไกการล็อคอื่น ๆ ที่พวกเขาไม่ได้เข้ามาใหม่ นี่ไม่เป็นความจริง.การล็อคที่ได้มาจากการ 'ซิงโครไนซ์' นั้นเป็นการเข้าใหม่ใน Java

ความแตกต่างที่สำคัญคือ 'ซิงโครไนซ์' ใช้การล็อคที่แท้จริง (หนึ่งที่ทุกวัตถุมี) ในขณะที่ล็อค API ไม่


0

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.