หลีกเลี่ยงการซิงโครไนซ์ (นี่) ใน Java หรือไม่


381

เมื่อใดก็ตามที่มีคำถามปรากฏขึ้นบน SO เกี่ยวกับการซิงโครไนซ์ Java บางคนมีความกระตือรือร้นที่จะชี้ให้เห็นว่าsynchronized(this)ควรหลีกเลี่ยง แต่พวกเขาอ้างว่าต้องการล็อคตัวอ้างอิงส่วนตัวแทน

เหตุผลบางประการที่กำหนดคือ:

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

ฉันสนใจที่จะเห็นตัวอย่างในโลกแห่งความเป็นจริง (ไม่มีสิ่งใดสำหรับ foobar) ที่หลีกเลี่ยงการล็อกบนthisจะดีกว่าเมื่อsynchronized(this)จะทำงาน

ดังนั้น: คุณควรหลีกเลี่ยงsynchronized(this)และแทนที่ด้วยล็อคในการอ้างอิงส่วนตัวหรือไม่?


ข้อมูลเพิ่มเติมบางส่วน (อัปเดตเมื่อได้รับคำตอบแล้ว):

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

4
ฉันต้องการจะชี้ให้เห็นว่าบริบทมีความสำคัญบิต "โดยทั่วไปแล้วมันคือตัวล็อคอินสแตนซ์เอง" อยู่ในส่วนที่เกี่ยวกับการจัดทำเอกสารคลาสเธรดที่ปลอดภัยตามเงื่อนไขเมื่อคุณทำให้การล็อกเป็นแบบสาธารณะ กล่าวอีกนัยหนึ่งประโยคนั้นใช้เมื่อคุณได้ทำการตัดสินใจครั้งนี้แล้ว
Jon Skeet

ในกรณีที่ไม่มีการซิงก์ภายในและเมื่อจำเป็นต้องซิงก์จากภายนอกการล็อคมักจะเป็นตัวอย่างของตัวเอง เหตุใดจึงไม่เป็นเช่นนี้สำหรับการซิงค์ภายในที่มีการล็อก 'นี่' เช่นกัน (ความสำคัญของเอกสารเป็นปัญหาอื่น.)
eljenso

มีการแลกเปลี่ยนระหว่าง granularity ที่ขยายเพิ่มและ CPU แคชและบัสค่าใช้จ่ายเพิ่มเติมเนื่องจากการล็อกวัตถุภายนอกอาจต้องการบรรทัดแคชแยกต่างหากเพื่อแก้ไขและแลกเปลี่ยนระหว่างแคช CPU (cf MESIF และ MOESI)
ArtemGr

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

คำตอบ:


132

ฉันจะครอบคลุมแต่ละจุดแยกกัน

  1. รหัสความชั่วร้ายบางอย่างอาจขโมยล็อคของคุณ (เป็นที่นิยมมากรหัสนี้มีตัวแปร "โดยไม่ตั้งใจ")

    ฉันกังวลเพิ่มเติมเกี่ยวกับการตั้งใจ สิ่งที่สำคัญคือการใช้งานนี้thisเป็นส่วนหนึ่งของอินเทอร์เฟซที่เปิดเผยของชั้นเรียนและควรได้รับการบันทึกไว้ บางครั้งต้องการความสามารถในการใช้รหัสอื่นของคุณล็อค สิ่งนี้เป็นจริงในสิ่งต่าง ๆ เช่นCollections.synchronizedMap(ดู javadoc)

  2. วิธีการซิงโครไนซ์ทั้งหมดภายในคลาสเดียวกันใช้การล็อกเดียวกันที่แน่นอนซึ่งช่วยลดปริมาณงาน

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

  3. คุณกำลังเปิดเผยข้อมูลมากเกินไปโดยไม่จำเป็น

    นี่คือตัวแปรของ # 1 การใช้synchronized(this)เป็นส่วนหนึ่งของอินเทอร์เฟซของคุณ หากคุณไม่ต้องการ / ต้องการสิ่งที่เปิดเผยอย่าทำเช่นนั้น


1. "ทำข้อมูลให้ตรงกัน" ไม่ได้เป็นส่วนหนึ่งของอินเทอร์เฟซที่เปิดเผยของคลาส 2. เห็นด้วย 3. ดู 1.
eljenso

66
การซิงโครไนซ์เป็นหลัก (นี้) ถูกเปิดเผยเนื่องจากหมายความว่ารหัสภายนอกสามารถส่งผลกระทบต่อการทำงานของคลาสของคุณ ดังนั้นฉันยืนยันว่าคุณต้องบันทึกเป็นอินเทอร์เฟซแม้ว่าภาษาจะไม่
Darron

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

3
ในความเป็นจริง Collections.synchronizedMap () ไม่ได้ใช้การซิงโครไนซ์ (นี้) ภายในจะใช้วัตถุล็อคสุดท้ายส่วนตัว
Bas Leijdekkers

1
@Bas Leijdekkers: เอกสารระบุอย่างชัดเจนว่าการซิงโครไนซ์เกิดขึ้นในอินสแตนซ์แผนที่ที่ส่งคืน สิ่งที่น่าสนใจคือมุมมองที่คืนมาkeySet()และvalues()ไม่ล็อค (ของพวกเขา) thisแต่อินสแตนซ์แผนที่ซึ่งเป็นสิ่งสำคัญที่จะได้รับพฤติกรรมที่สอดคล้องกันสำหรับการดำเนินงานแผนที่ทั้งหมด เหตุผลที่วัตถุล็อคถูกแยกออกเป็นตัวแปรคือคลาสย่อยSynchronizedSortedMapต้องการมันสำหรับการใช้งานแผนที่ย่อยที่ล็อคอินสแตนซ์แผนที่เดิม
Holger

86

ก่อนอื่นควรชี้ให้เห็นว่า:

public void blah() {
  synchronized (this) {
    // do stuff
  }
}

เทียบเท่ากับความหมาย:

public synchronized void blah() {
  // do stuff
}

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

การล็อคส่วนตัวเป็นกลไกการป้องกันซึ่งเป็นความคิดที่ไม่เลว

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

synchronized(this) แค่ไม่ได้ให้อะไรเลย


4
"ทำข้อมูลให้ตรงกัน (นี่) แค่ไม่ได้ให้อะไรคุณเลย" ตกลงฉันแทนที่ด้วยซิงโครไนซ์ (myPrivateFinalLock) นั่นให้อะไรฉัน คุณพูดถึงมันเป็นกลไกการป้องกัน ฉันได้รับการคุ้มครองอะไรบ้าง
eljenso

14
คุณได้รับการปกป้องจากการล็อกวัตถุนี้จากภายนอก (หรือที่เป็นอันตราย) โดยไม่ตั้งใจ
cletus

14
ฉันไม่เห็นด้วยเลยกับคำตอบนี้: ควรล็อคไว้เสมอในระยะเวลาที่สั้นที่สุดและนั่นคือเหตุผลที่คุณต้องการ "ทำสิ่ง" รอบ ๆ บล็อกที่ซิงโครไนซ์แทนการซิงโครไนซ์วิธีทั้งหมด .
Olivier

5
การทำสิ่งนอกบล็อกที่ซิงโครไนซ์นั้นมีเจตนาดีเสมอ ประเด็นก็คือผู้คนจะทำสิ่งนี้ผิดบ่อยครั้งและไม่รู้ตัวเหมือนในปัญหาการล็อคซ้ำซ้อน ถนนสู่นรกปูด้วยเจตนาดี
cletus

16
ฉันไม่เห็นด้วยโดยทั่วไปกับ "X เป็นกลไกการป้องกันซึ่งไม่เคยมีความคิดที่ดีเลย" มีโค้ดจำนวนมากป่องออกโดยไม่จำเป็นเนื่องจากทัศนคตินี้
finnw

54

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

สมมติว่ารหัสต่อไปนี้:

public void method1() {
    // do something ...
    synchronized(this) {
        a ++;      
    }
    // ................
}


public void method2() {
    // do something ...
    synchronized(this) {
        b ++;      
    }
    // ................
}

วิธีที่ 1 การปรับเปลี่ยนตัวแปรaและวิธีที่ 2 การปรับเปลี่ยนตัวแปรbควรหลีกเลี่ยงการแก้ไขตัวแปรเดียวกันพร้อมกันโดยสองเธรดพร้อมกันและเป็น แต่ในขณะที่thread1 กำลังแก้ไขaและthread2 กำลังแก้ไขbมันสามารถทำได้โดยไม่มีเงื่อนไขการแย่งชิงใด ๆ

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

วิธีแก้ปัญหาคือใช้การล็อกต่างกัน2แบบสำหรับตัวแปรที่ต่างกันสองตัว:

public class Test {

    private Object lockA = new Object();
    private Object lockB = new Object();

    public void method1() {
        // do something ...
        synchronized(lockA) {
            a ++;      
        }
        // ................
    }


    public void method2() {
        // do something ...
        synchronized(lockB) {
            b ++;      
        }
        // ................
    }

}

ตัวอย่างด้านบนใช้การล็อกที่ละเอียดยิ่งขึ้น (2 ล็อกแทนหนึ่งรายการ ( lockA และlockBสำหรับตัวแปรaและbตามลำดับ) และเป็นผลลัพธ์ที่ช่วยให้การทำงานพร้อมกันดีขึ้นในทางกลับกันมันซับซ้อนกว่าตัวอย่างแรก ...


มันอันตรายมาก ตอนนี้คุณได้แนะนำข้อกำหนดการสั่งล็อกสำหรับฝั่งไคลเอ็นต์ (ผู้ใช้ของคลาสนี้) หากสองเธรดกำลังเรียกใช้เมธอด 1 () และเมธอด 2 () ในลำดับที่ต่างกันพวกเขามีแนวโน้มที่จะหยุดชะงัก แต่ผู้ใช้ของคลาสนี้ไม่มีความคิดว่าเป็นกรณีนี้
daveb

7
ความละเอียดไม่ได้มาจาก "ทำข้อมูลให้ตรงกัน (นี่)" เกินขอบเขตของคำถามของฉัน และฟิลด์ล็อคของคุณไม่ควรเป็นครั้งสุดท้ายหรือ
eljenso

10
เพื่อให้มีการหยุดชะงักเราควรทำการโทรจากบล็อกที่ซิงโครไนซ์โดย A ไปยังบล็อกที่ซิงโครไนซ์โดย B. daveb คุณคิดผิด ...
Andreas Bakurov

3
ในตัวอย่างนี้ไม่มีการหยุดชะงักเท่าที่ฉันเห็น ฉันยอมรับมันเป็นเพียง pseudocode แต่ฉันจะใช้การใช้งานอย่างใดอย่างหนึ่งของ java.util.concurrent.locks.Lock เช่น java.util.concurrent.locks.ReentrantLock
Shawn Vader

15

ในขณะที่ฉันเห็นด้วยเกี่ยวกับการไม่ปฏิบัติตามกฎเกณฑ์แบบสุ่มสี่สุ่มห้าสถานการณ์จำลอง "ล็อคการขโมย" ดูเหมือนจะผิดปกติกับคุณหรือไม่ เธรดอาจได้รับการล็อควัตถุของคุณ "ภายนอก" ( synchronized(theObject) {...}), การปิดกั้นเธรดอื่น ๆ ที่รอวิธีการทำข้อมูลให้ตรงกัน

หากคุณไม่เชื่อในโค้ดที่เป็นอันตรายให้พิจารณาว่ารหัสนี้อาจมาจากบุคคลที่สาม (ตัวอย่างเช่นหากคุณพัฒนาแอปพลิเคชันเซิร์ฟเวอร์บางประเภท)

เวอร์ชั่น "บังเอิญ" ดูเหมือนจะมีโอกาสน้อยลง แต่อย่างที่พวกเขาพูดว่า "ทำอะไรที่ไม่งี่เง่าและใครบางคนจะประดิษฐ์ไอเดียที่ดีกว่า"

ดังนั้นฉันจึงเห็นด้วยกับโรงเรียนแห่งความคิดที่ขึ้นอยู่กับสิ่งที่ทำ


แก้ไขความคิดเห็น 3 รายการแรกของ eljenso ต่อไปนี้:

ฉันไม่เคยประสบปัญหาการขโมยล็อค แต่นี่เป็นสถานการณ์สมมติในจินตนาการ:

สมมติว่าระบบของคุณเป็น servlet container และวัตถุที่เรากำลังพิจารณาคือServletContextการนำไปใช้ ใช้getAttributeวิธีการที่จะต้องด้ายปลอดภัยเป็นคุณลักษณะบริบทเป็นข้อมูลที่ใช้ร่วมกัน synchronizedเพื่อให้คุณประกาศว่ามันเป็น ลองจินตนาการอีกว่าคุณให้บริการโฮสติ้งสาธารณะตามการใช้งานคอนเทนเนอร์ของคุณ

ฉันเป็นลูกค้าของคุณและปรับใช้เซิร์ฟเล็ต "ดี" ในเว็บไซต์ของคุณ getAttributeมันเกิดขึ้นว่ารหัสของฉันมีการเรียกไปยัง

แฮ็กเกอร์ซึ่งปลอมตัวเป็นลูกค้ารายอื่นปรับใช้เซิร์ฟเล็ตที่เป็นอันตรายของเขาในเว็บไซต์ของคุณ มันมีรหัสต่อไปนี้ในinitวิธีการ:

ทำข้อมูลให้ตรงกัน (this.getServletConfig (). getServletContext ()) {
   ในขณะที่ (จริง) {}
}

สมมติว่าเราแบ่งปันบริบท servlet เดียวกัน (อนุญาตโดย spec ตราบเท่าที่ทั้งสอง servlets อยู่บนโฮสต์เสมือนเดียวกัน) การโทรของฉันgetAttributeถูกล็อคตลอดไป แฮกเกอร์ประสบความสำเร็จกับ DoS บน servlet ของฉัน

การโจมตีนี้เป็นไปไม่ได้หากgetAttributeทำข้อมูลให้ตรงกันในการล็อคส่วนตัวเนื่องจากรหัสบุคคลที่สามไม่สามารถรับการล็อคนี้

ฉันยอมรับว่าตัวอย่างนี้ถูกออกแบบมาและมีมุมมองแบบ oversimplistic ว่า servlet container ทำงานอย่างไร แต่ IMHO ก็พิสูจน์ได้

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


มันขึ้นอยู่กับสิ่งที่คลาส: ถ้ามันเป็นวัตถุ 'สำคัญ' แล้วล็อคการอ้างอิงส่วนตัว? การล็อกอินสแตนซ์อื่นจะเพียงพอหรือไม่
eljenso

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

4
นอกจากนี้การล็อคการอ้างอิงภายในนั้นไม่ได้ปลอดจาก "การโจมตีจากซิงค์ภายนอก": หากคุณรู้ว่าส่วนที่ซิงค์ของรหัสรอให้เหตุการณ์ภายนอกเกิดขึ้น (เช่นการเขียนไฟล์ค่าใน DB เหตุการณ์ตัวจับเวลา) คุณสามารถ จัดให้มันเพื่อป้องกันเช่นกัน
eljenso

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

12

มันขึ้นอยู่กับสถานการณ์
หากมีนิติบุคคลเดียวเท่านั้นหรือมากกว่าหนึ่งองค์กร

ดูตัวอย่างการทำงานได้ ที่นี่

การแนะนำเล็กน้อย

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

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

วากยสัมพันธ์

synchronized(this)
{
  SHARED_ENTITY.....
}

"this" จัดเตรียมการล็อกที่แท้จริงที่เชื่อมโยงกับคลาส (นักพัฒนา Java ที่ออกแบบคลาสอ็อบเจ็กต์ในลักษณะที่แต่ละอ็อบเจ็กต์สามารถทำงานเป็นมอนิเตอร์) วิธีการข้างต้นใช้งานได้ดีเมื่อมีเอนทิตีที่แชร์เพียงหนึ่งเดียวและหลายเธรด (1: N) เอ็นทิตี้ที่แบ่งปันได้ของ M-M ตอนนี้คิดถึงสถานการณ์เมื่อมีอ่างล้างหน้าสองอ่างในห้องน้ำและประตูเดียวเท่านั้น หากเราใช้วิธีการก่อนหน้าเฉพาะ p1 เท่านั้นที่สามารถใช้หนึ่งอ่างล้างหน้าในแต่ละครั้งในขณะที่ p2 จะรออยู่ข้างนอก เป็นการสิ้นเปลืองทรัพยากรเพราะไม่มีใครใช้ B2 (อ่างล้างหน้า) แนวทางที่ชาญฉลาดคือการสร้างห้องขนาดเล็กภายในห้องน้ำและให้พวกเขาหนึ่งประตูต่ออ่างล้างหน้า ด้วยวิธีนี้ P1 สามารถเข้าถึง B1 และ P2 สามารถเข้าถึง B2 และในทางกลับกัน
ป้อนคำอธิบายรูปภาพที่นี่

washbasin1;  
washbasin2;

Object lock1=new Object();
Object lock2=new Object();

  synchronized(lock1)
  {
    washbasin1;
  }

  synchronized(lock2)
  {
    washbasin2;
  }

ป้อนคำอธิบายรูปภาพที่นี่
ป้อนคำอธิบายรูปภาพที่นี่

ดูเพิ่มเติมเกี่ยวกับเธรด ----> ที่นี่


11

ดูเหมือนว่าฉันทามติต่างกันในค่าย C # และ Java ในเรื่องนี้ ส่วนใหญ่ของรหัส Java ฉันเห็นการใช้:

// apply mutex to this instance
synchronized(this) {
    // do work here
}

ในขณะที่ส่วนใหญ่ของรหัส C # opts สำหรับปลอดภัยเนื้อหา:

// instance level lock object
private readonly object _syncObj = new object();

...

// apply mutex to private instance level field (a System.Object usually)
lock(_syncObj)
{
    // do work here
}

สำนวน C # ปลอดภัยกว่าอย่างแน่นอน ดังที่ได้กล่าวไปแล้วก่อนหน้านี้ไม่สามารถเข้าถึงการล็อคที่เป็นอันตราย / โดยไม่ตั้งใจจากภายนอกได้ รหัส Java ก็มีความเสี่ยงเช่นกันแต่ดูเหมือนว่าชุมชน Java ได้รับความสนใจเมื่อเวลาผ่านไปซึ่งมีความปลอดภัยน้อยกว่าเล็กน้อย

นั่นไม่ได้หมายถึงการขุดกับ Java เพียงแค่สะท้อนให้เห็นถึงประสบการณ์ของฉันทำงานกับทั้งสองภาษา


3
บางทีอาจเป็นเพราะ C # เป็นภาษาที่อายุน้อยกว่าพวกเขาเรียนรู้จากรูปแบบที่ไม่ดีที่คิดในค่าย Java และรหัสสิ่งนี้ดีขึ้นหรือไม่ มีซิงเกิลตันน้อยลงหรือไม่? :)
Bill K

3
ฮิฮิ. ค่อนข้างจะเป็นจริง แต่ฉันจะไม่ไปหาเหยื่อ! หนึ่งคิดว่าฉันสามารถพูดได้อย่างชัดเจนก็คือว่ามีมากขึ้นตัวอักษรในรหัส C #;)
serg10

1
เพียงแค่ไม่เป็นความจริง (ที่จะนำมันอย่าง)
tcurdt

7

java.util.concurrentแพคเกจได้ลดลงอย่างมากมายซับซ้อนของด้ายรหัสความปลอดภัยของฉัน ฉันมีหลักฐานพอสังเขปที่จะดำเนินต่อไป แต่งานส่วนใหญ่ที่ฉันเห็นด้วยsynchronized(x)ดูเหมือนจะเป็นการใช้งานล็อคเซมาฟอร์หรือแลตช์อีกครั้ง แต่ใช้จอมอนิเตอร์ระดับล่าง

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


6
  1. ทำให้ข้อมูลของคุณไม่เปลี่ยนรูปถ้าเป็นไปได้ ( finalตัวแปร)
  2. หากคุณไม่สามารถหลีกเลี่ยงการกลายพันธุ์ของข้อมูลที่ใช้ร่วมกันในหลาย ๆ เธรดให้ใช้การสร้างโปรแกรมระดับสูง [เช่น granular LockAPI]

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

โค้ดตัวอย่างที่จะใช้ReentrantLockซึ่งนำLockอินเตอร์เฟสมาใช้

 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

ข้อดีของการล็อกการซิงโครไนซ์ (สิ่งนี้)

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

  2. การใช้งานล็อคให้ฟังก์ชั่นเพิ่มเติมมากกว่าการใช้วิธีการที่ตรงและงบโดยการให้

    1. ความพยายามที่ไม่บล็อกเพื่อรับล็อค (tryLock() )
    2. ความพยายามที่จะได้รับการล็อคที่สามารถถูกขัดจังหวะ (lockInterruptibly() )
    3. ความพยายามที่จะได้รับการล็อคที่สามารถหมดเวลา ( tryLock(long, TimeUnit))
  3. คลาสล็อคยังสามารถให้ลักษณะการทำงานและความหมายที่ค่อนข้างแตกต่างจากล็อคของจอภาพโดยปริยายเช่น

    1. รับประกันการสั่งซื้อ
    2. การใช้งานที่ไม่ได้เข้าใหม่
    3. การตรวจจับการหยุดชะงัก

ดูคำถาม SE นี้เกี่ยวกับประเภทต่างๆของLocks:

การประสานเทียบกับล็อค

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

Lock Objectsรองรับการล็อกสำนวนที่ทำให้การใช้งานที่เกิดขึ้นพร้อมกันจำนวนมากง่ายขึ้น

ผู้บริหารกำหนด API ระดับสูงสำหรับการเรียกใช้และการจัดการเธรด การปรับใช้ Executor ที่จัดทำโดย java.util.concurrent จัดเตรียมการจัดการเธรดพูลที่เหมาะสำหรับแอ็พพลิเคชันขนาดใหญ่

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

ตัวแปรอะตอมมิกมีคุณสมบัติที่ลดการซิงโครไนซ์และช่วยหลีกเลี่ยงข้อผิดพลาดที่สอดคล้องกันของหน่วยความจำ

ThreadLocalRandom (ใน JDK 7) จัดเตรียมการสร้างหมายเลขเทียมที่มีประสิทธิภาพจากหลายเธรด

อ้างถึงjava.util.concurrentและjava.util.concurrent.atomicแพ็คเกจเกินไปสำหรับการเขียนโปรแกรมการสร้างอื่น ๆ


5

หากคุณตัดสินใจแล้ว:

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

ถ้าอย่างนั้นฉันไม่เห็น taboo over synchronizezd (อันนี้)

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

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


1
"ถ้าตรรกะนี้คาดว่าจะเป็น ... " เป็นจุดที่ฉันพยายามข้ามเช่นกัน ฉันไม่เห็นจุดที่ใช้ล็อคส่วนตัวอยู่เสมอแม้ว่าฉันทามติทั่วไปน่าจะดีกว่าเพราะมันไม่เจ็บและป้องกันมากกว่า
eljenso

4

ฉันคิดว่ามีคำอธิบายที่ดีว่าทำไมแต่ละเทคนิคเหล่านี้จึงเป็นเทคนิคที่สำคัญภายใต้เข็มขัดของคุณในหนังสือชื่อ Java Concurrency In Practice โดย Brian Goetz เขาทำให้จุดหนึ่งชัดเจนมาก - คุณต้องใช้ล็อคเดียวกัน "ทุกที่" เพื่อป้องกันสถานะของวัตถุของคุณ วิธีการซิงโครไนซ์และการซิงโครไนซ์บนวัตถุมักจะจับมือกัน เช่น Vector จะซิงโครไนซ์วิธีการทั้งหมด หากคุณมีมือจับวัตถุเวกเตอร์และกำลังจะทำ "ใส่ถ้าไม่ได้" แล้วเพียงแค่เวกเตอร์ซิงโครไนซ์วิธีการของตัวเองไม่ได้จะปกป้องคุณจากการทุจริตของรัฐ คุณต้องทำการซิงโครไนซ์โดยใช้การซิงโครไนซ์ (vectorHandle) ซึ่งจะส่งผลให้เกิดการล็อค SAME โดยเธรดทุกตัวที่มีจุดจับกับเวกเตอร์และจะป้องกันสถานะโดยรวมของเวกเตอร์ สิ่งนี้เรียกว่าการล็อกฝั่งไคลเอ็นต์ เรารู้ว่าในความเป็นจริงเวกเตอร์ทำการซิงโครไนซ์ (นี่) / ซิงโครไนซ์วิธีการทั้งหมดและการซิงโครไนซ์บนวัตถุ vectorHandle จะส่งผลให้การซิงโครไนซ์สถานะวัตถุเวกเตอร์ที่เหมาะสม เป็นเรื่องโง่ที่จะเชื่อว่าคุณเป็นเธรดที่ปลอดภัยเพราะคุณใช้คอลเลกชันเธรดที่ปลอดภัย นี่คือเหตุผลที่แม่นยำ ConcurrentHashMap แนะนำวิธี put ifAbsent อย่างชัดเจน - เพื่อให้การดำเนินการดังกล่าวเป็นอะตอม

สรุป

  1. การซิงโครไนซ์ที่ระดับวิธีการช่วยให้การล็อคฝั่งไคลเอ็นต์
  2. หากคุณมีวัตถุล็อคส่วนตัว - มันทำให้การล็อคฝั่งไคลเอ็นต์เป็นไปไม่ได้ นี่เป็นเรื่องปกติถ้าคุณรู้ว่าชั้นเรียนของคุณไม่มีฟังก์ชันการทำงานแบบ "ไม่ใส่"
  3. หากคุณกำลังออกแบบไลบรารี - การซิงโครไนซ์กับสิ่งนี้หรือการซิงโครไนซ์วิธีนั้นมักฉลาดกว่า เพราะคุณไม่ค่อยอยู่ในตำแหน่งที่จะตัดสินใจว่าจะใช้คลาสของคุณอย่างไร
  4. หาก Vector ใช้วัตถุล็อคส่วนตัว - คงเป็นไปไม่ได้เลยที่จะเอา รหัสลูกค้าจะไม่ได้รับการจัดการการล็อคส่วนตัวจึงทำลายกฎพื้นฐานของการใช้ EXACT SAME LOCK เพื่อปกป้องสถานะของมัน
  5. การซิงโครไนซ์บนวิธีนี้หรือวิธีการซิงโครไนซ์มีปัญหาตามที่คนอื่น ๆ ชี้ให้เห็น - บางคนสามารถล็อคและไม่ปล่อย หัวข้ออื่น ๆ ทั้งหมดจะรอการปลดล็อคอยู่
  6. ดังนั้นจงรู้ว่าคุณกำลังทำอะไรและยอมรับสิ่งที่ถูกต้อง
  7. มีคนแย้งว่าการมีวัตถุล็อคส่วนตัวช่วยให้คุณมีความละเอียดมากขึ้น - เช่นหากการดำเนินการสองอย่างไม่เกี่ยวข้องกัน - พวกเขาอาจได้รับการปกป้องจากการล็อคที่แตกต่างกันทำให้ได้ปริมาณงานที่ดีขึ้น แต่นี่ฉันคิดว่าเป็นกลิ่นการออกแบบและไม่ได้กลิ่นรหัส - หากการปฏิบัติการสองอย่างไม่เกี่ยวข้องอย่างสมบูรณ์ทำไมพวกเขาถึงเป็นส่วนหนึ่งของคลาสเดียวกัน ทำไมคลาสสโมสรไม่เกี่ยวข้องกับฟังก์ชันการทำงานเลย? อาจเป็นคลาสยูทิลิตี้ Hmmmm - ประโยชน์บางอย่างให้การจัดการสตริงและวันที่จัดรูปแบบปฏิทินผ่านอินสแตนซ์เดียวกัน ... อย่างน้อยก็ไม่สมเหตุสมผลกับฉันเลย !!

3

ไม่คุณไม่ควรทำเสมอไป อย่างไรก็ตามฉันมักจะหลีกเลี่ยงเมื่อมีข้อกังวลหลายประการในวัตถุเฉพาะที่ต้องเป็น threadsafe เฉพาะส่วนของตัวเองเท่านั้น ตัวอย่างเช่นคุณอาจมีวัตถุข้อมูลที่ไม่แน่นอนซึ่งมีฟิลด์ "label" และ "parent" สิ่งเหล่านี้จำเป็นต้องเป็น threadsafe แต่การเปลี่ยนแปลงอย่างใดอย่างหนึ่งไม่จำเป็นต้องปิดกั้นไม่ให้ถูกเขียน / อ่าน (ในทางปฏิบัติฉันจะหลีกเลี่ยงสิ่งนี้โดยการประกาศฟิลด์ที่ผันผวนและ / หรือการใช้ตัวห่อ AtomicFoo ของ java.util.concurrent)

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

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


3

คำตอบสั้น ๆ : คุณต้องเข้าใจความแตกต่างและเลือกขึ้นอยู่กับรหัส

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


ฉันเข้าใจคุณถูกต้องหรือไม่เมื่อคุณพูดว่ามันขึ้นอยู่กับประสบการณ์ของคุณ?
eljenso

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

2

ล็อคใช้สำหรับการมองเห็นหรือปกป้องข้อมูลบางอย่างจากการปรับเปลี่ยนพร้อมกันซึ่งอาจนำไปสู่การแข่งขัน

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

แต่สมมติว่าคุณมีจำนวนเต็มสองจำนวนที่เกี่ยวข้องกับแต่ละอื่น ๆ เช่นการxและyประสานงานที่เกี่ยวข้องกับแต่ละอื่น ๆ และควรจะมีการเปลี่ยนแปลงในลักษณะที่อะตอม จากนั้นคุณจะปกป้องพวกเขาโดยใช้การล็อคเดียวกัน

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

class Point{
   private int x;
   private int y;

   public Point(int x, int y){
       this.x = x;
       this.y = y;
   }

   //mutating methods should be guarded by same lock
   public synchronized void changeCoordinates(int x, int y){
       this.x = x;
       this.y = y;
   }
}

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

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

ในตอนนี้ตรงกันข้ามกับPointตัวอย่างมีตัวอย่างของTwoCounters@Andreas ที่รัฐได้รับการปกป้องโดยการล็อคสองแบบที่แตกต่างกันเนื่องจากรัฐไม่เกี่ยวข้องกัน

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


1

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

จากมุมมองของผู้อ่านหากคุณเห็นการล็อคสิ่งนี้คุณต้องตอบคำถามสองข้อเสมอ:

  1. สิ่งที่ชนิดของการเข้าถึงการคุ้มครองตามนี้ ?
  2. ล็อคหนึ่งเพียงพอจริงๆไม่มีใครแนะนำข้อผิดพลาดหรือไม่

ตัวอย่าง:

class BadObject {
    private Something mStuff;
    synchronized setStuff(Something stuff) {
        mStuff = stuff;
    }
    synchronized getStuff(Something stuff) {
        return mStuff;
    }
    private MyListener myListener = new MyListener() {
        public void onMyEvent(...) {
            setStuff(...);
        }
    }
    synchronized void longOperation(MyListener l) {
        ...
        l.onMyEvent(...);
        ...
    }
}

หากสองเธรดเริ่มต้นlongOperation()บนอินสแตนซ์ที่ต่างกันสองรายการBadObjectพวกเขาจะได้รับการล็อก เมื่อถึงเวลาที่จะเรียกใช้l.onMyEvent(...)เรามีการหยุดชะงักเนื่องจากไม่มีเธรดใดอาจได้รับการล็อคของวัตถุอื่น

ในตัวอย่างนี้เราอาจกำจัดการหยุดชะงักโดยใช้สองล็อคหนึ่งสำหรับการทำงานสั้นและอีกหนึ่งสำหรับยาว


2
วิธีเดียวในการรับการหยุดชะงักในตัวอย่างนี้คือเมื่อBadObjectA เรียกใช้longOperationB ผ่านการส่ง A myListenerและในทางกลับกัน ไม่ใช่เป็นไปไม่ได้ แต่ค่อนข้างจะซับซ้อนสนับสนุนประเด็นก่อนหน้านี้ของฉัน
eljenso

1

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

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

คำอธิบายโดยละเอียดเพิ่มเติมเกี่ยวกับความแตกต่างที่คุณสามารถพบได้ที่นี่: http://www.artima.com/insidejvm/ed2/threadsynchP.html

การใช้ซิงโครไนซ์บล็อกไม่ดีเนื่องจากมุมมองต่อไปนี้:

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

สำหรับรายละเอียดเพิ่มเติมในส่วนนี้ฉันอยากจะแนะนำให้คุณอ่านบทความนี้: http://java.dzone.com/articles/synchronized-considered


1

นี่เป็นเพียงการเสริมคำตอบอื่น ๆ แต่ถ้าการคัดค้านหลักของคุณในการใช้วัตถุส่วนตัวสำหรับล็อคคือมันตัดคลาสของคุณด้วยฟิลด์ที่ไม่เกี่ยวข้องกับตรรกะทางธุรกิจ Project Lombok @Synchronizedต้องสร้างสำเร็จรูปในเวลารวบรวม:

@Synchronized
public int foo() {
    return 0;
}

รวบรวมเพื่อ

private final Object $lock = new Object[0];

public int foo() {
    synchronized($lock) {
        return 0;
    }
}

0

ตัวอย่างที่ดีสำหรับการใช้ข้อมูลให้ตรงกัน (นี่)

// add listener
public final synchronized void addListener(IListener l) {listeners.add(l);}
// remove listener
public final synchronized void removeListener(IListener l) {listeners.remove(l);}
// routine that raise events
public void run() {
   // some code here...
   Set ls;
   synchronized(this) {
      ls = listeners.clone();
   }
   for (IListener l : ls) { l.processEvent(event); }
   // some code here...
}

ดังที่คุณเห็นที่นี่เราใช้การซิงโครไนซ์กับสิ่งนี้เพื่อให้ความร่วมมือกับวิธีการแบบยาวอย่างไม่มีที่สิ้นสุด

แน่นอนว่ามันสามารถเขียนใหม่ได้อย่างง่ายดายด้วยการใช้การซิงโครไนซ์บนฟิลด์ส่วนตัว แต่บางครั้งเมื่อเรามีการออกแบบบางอย่างด้วยวิธีการซิงโครไนซ์ (เช่นคลาสดั้งเดิมเราได้รับมาจากการซิงโครไนซ์ (นี่) อาจเป็นทางออกเดียว)


วัตถุใด ๆ ที่สามารถใช้เป็นล็อคได้ที่นี่ thisมันไม่จำเป็นต้องเป็น มันอาจเป็นสนามส่วนตัว
finnw

ถูกต้อง แต่วัตถุประสงค์ของตัวอย่างนี้คือเพื่อแสดงว่าการซิงโครไนซ์อย่างถูกต้องอย่างไรถ้าเราตัดสินใจใช้วิธีการซิงโครไนซ์
Bart Prokop

0

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


0

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

// Synchronization objects (locks)
private static HashMap<String, Object> locks = new HashMap<String, Object>();
// Simple method
private static Object atomic() {
    StackTraceElement [] stack = Thread.currentThread().getStackTrace(); // get execution point 
    StackTraceElement exepoint = stack[2];
    // creates unique key from class name and line number using execution point
    String key = String.format("%s#%d", exepoint.getClassName(), exepoint.getLineNumber()); 
    Object lock = locks.get(key); // use old or create new lock
    if (lock == null) {
        lock = new Object();
        locks.put(key, lock);
    }
    return lock; // return reference to lock
}
// Synchronized code
void dosomething1() {
    // start commands
    synchronized (atomic()) {
        // atomic commands 1
        ...
    }
    // other command
}
// Synchronized code
void dosomething2() {
    // start commands
    synchronized (atomic()) {
        // atomic commands 2
        ...
    }
    // other command
}

0

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

   public void foo() {
if(operation = null) {
    synchronized(foo) { 
if (operation == null) {
 // enter your code that this method has to handle...
          }
        }
      }
    }

0

สองเซ็นต์ของฉันในปี 2019 แม้ว่าคำถามนี้อาจจะได้รับการตัดสินแล้ว

การล็อกที่ 'นี่' นั้นไม่เลวเลยถ้าคุณรู้ว่าคุณกำลังทำอะไรอยู่ แต่เบื้องหลังการล็อกที่ 'นี่' ก็คือ

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

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

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

ไม่มีใครโต้แย้งได้ว่า 'โดยทั่วไป' คุณไม่ต้องการให้ผู้ใช้ในชั้นเรียนของคุณสามารถทำสิ่งเหล่านี้หรือ 'ปกติ' ที่คุณต้องการ ... ขึ้นอยู่กับฟังก์ชันการทำงานที่คุณกำลังเข้ารหัส คุณไม่สามารถสร้างกฎง่ายๆได้เนื่องจากคุณไม่สามารถทำนายกรณีการใช้ทั้งหมดได้

ลองพิจารณาตัวอย่างเช่น printwriter ที่ใช้การล็อกภายใน แต่คนก็พยายามใช้มันจากหลาย ๆ เธรดถ้าพวกเขาไม่ต้องการให้เอาท์พุตแทรก

หากล็อคของคุณสามารถเข้าถึงได้นอกชั้นเรียนหรือไม่เป็นการตัดสินใจของคุณในฐานะโปรแกรมเมอร์บนพื้นฐานของฟังก์ชั่นที่มีในชั้นเรียน มันเป็นส่วนหนึ่งของ API คุณไม่สามารถย้ายออกจากการซิงโครไนซ์ (นี่) เป็นการซิงโครไนซ์ (provateObjet) โดยไม่เสี่ยงต่อการเปลี่ยนแปลงในการใช้โค้ด

หมายเหตุ 1: ฉันรู้ว่าคุณสามารถบรรลุสิ่งที่ทำข้อมูลให้ตรงกัน (นี้) 'บรรลุ' โดยใช้วัตถุล็อคชัดเจนและเปิดเผย แต่ฉันคิดว่ามันไม่จำเป็นถ้าพฤติกรรมของคุณได้รับการบันทึกไว้เป็นอย่างดีและคุณรู้จริง ๆ ว่าการล็อก

หมายเหตุ 2: ฉันไม่เห็นด้วยกับข้อโต้แย้งว่าถ้ารหัสบางอย่างตั้งใจขโมยล็อคของคุณมันเป็นข้อผิดพลาดและคุณต้องแก้ไขมัน นี่เป็นข้อโต้แย้งเดียวกับที่บอกว่าฉันสามารถทำให้วิธีการทั้งหมดของฉันเปิดเผยต่อสาธารณะแม้ว่าพวกเขาจะไม่ได้ตั้งใจที่จะเปิดเผย หากมีคน 'ตั้งใจ' โทรหาฉันตั้งใจที่จะเป็นวิธีส่วนตัวมันเป็นข้อผิดพลาด ทำไมเปิดใช้งานอุบัติเหตุนี้ตั้งแต่แรก !!! หากความสามารถในการขโมยล็อคของคุณเป็นปัญหาสำหรับชั้นเรียนของคุณไม่อนุญาต ง่ายเหมือนที่


-3

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

มันไม่ได้อยู่ในหิน แต่ส่วนใหญ่เป็นปัญหาของการฝึกฝนที่ดีและการป้องกันข้อผิดพลาด

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