ฉันเห็นด้วยกับหนึ่งในความคิดเห็นของ John: คุณต้องใช้ตัวล็อกสุดท้ายเสมอในขณะที่เข้าถึงตัวแปรที่ไม่ใช่ขั้นสุดท้ายเพื่อป้องกันความไม่สอดคล้องกันในกรณีที่มีการเปลี่ยนแปลงการอ้างอิงของตัวแปร ดังนั้นไม่ว่าในกรณีใด ๆ และตามกฎข้อแรก:
กฎข้อที่ 1: หากฟิลด์ไม่เป็นที่สิ้นสุดให้ใช้ (ส่วนตัว) ดัมมี่ล็อกสุดท้าย (ส่วนตัว) เสมอ
เหตุผล # 1: คุณล็อคและเปลี่ยนการอ้างอิงของตัวแปรด้วยตัวเอง เธรดอื่นที่รออยู่นอกล็อกที่ซิงโครไนซ์จะสามารถเข้าสู่บล็อกที่มีการป้องกันได้
เหตุผล # 2: คุณล็อคไว้และเธรดอื่นเปลี่ยนการอ้างอิงของตัวแปร ผลลัพธ์จะเหมือนกัน: เธรดอื่นสามารถเข้าสู่บล็อกที่มีการป้องกันได้
แต่เมื่อใช้ดัมมี่ล็อคขั้นสุดท้ายมีปัญหาอีกอย่าง : คุณอาจได้รับข้อมูลที่ไม่ถูกต้องเนื่องจากอ็อบเจ็กต์ที่ไม่ใช่ขั้นสุดท้ายของคุณจะซิงโครไนซ์กับ RAM เมื่อเรียกซิงโครไนซ์ (วัตถุ) เท่านั้น ดังนั้นตามกฎข้อที่สอง:
กฎ # 2: เมื่อล็อควัตถุที่ไม่ใช่ขั้นสุดท้ายคุณจำเป็นต้องทำทั้งสองอย่างเสมอ: การใช้ดัมมี่ล็อคสุดท้ายและการล็อคของวัตถุที่ไม่ใช่ขั้นสุดท้ายเพื่อประโยชน์ในการซิงโครไนซ์ RAM (ทางเลือกเดียวคือการประกาศเขตข้อมูลทั้งหมดของวัตถุว่ามีความผันผวน!)
ล็อกเหล่านี้เรียกอีกอย่างว่า "ล็อกที่ซ้อนกัน" โปรดทราบว่าคุณต้องเรียกพวกเขาในลำดับเดียวกันเสมอมิฉะนั้นคุณจะได้รับล็อกตาย :
public class X {
private final LOCK;
private Object o;
public void setO(Object o){
this.o = o;
}
public void x() {
synchronized (LOCK) {
synchronized(o){
}
}
}
}
อย่างที่คุณเห็นฉันเขียนสองล็อคไว้ในบรรทัดเดียวกันโดยตรงเพราะมันอยู่ด้วยกันเสมอ ด้วยวิธีนี้คุณสามารถล็อค 10 รัง:
synchronized (LOCK1) {
synchronized (LOCK2) {
synchronized (LOCK3) {
synchronized (LOCK4) {
}
}
}
}
โปรดทราบว่ารหัสนี้จะไม่แตกหากคุณเพิ่งได้รับการล็อกด้านในเช่นเดียวsynchronized (LOCK3)
กับเธรดอื่น แต่มันจะพังถ้าคุณโทรในเธรดอื่นสิ่งนี้:
synchronized (LOCK4) {
synchronized (LOCK1) {
synchronized (LOCK3) {
synchronized (LOCK2) {
}
}
}
}
มีเพียงวิธีแก้ปัญหาเดียวสำหรับการล็อกที่ซ้อนกันดังกล่าวในขณะที่จัดการฟิลด์ที่ไม่ใช่ขั้นสุดท้าย:
กฎ # 2 - ทางเลือก: ประกาศทุกฟิลด์ของวัตถุว่าระเหยได้ (ฉันจะไม่พูดถึงข้อเสียของการทำเช่นนี้เช่นการป้องกันการจัดเก็บใด ๆ ในแคชระดับ x แม้กระทั่งสำหรับการอ่าน aso ก็ตาม)
ดังนั้น aioobe จึงค่อนข้างถูกต้อง: เพียงใช้ java.util.concurrent หรือเริ่มเข้าใจทุกอย่างเกี่ยวกับการซิงโครไนซ์และทำด้วยตัวเองด้วยการล็อกแบบซ้อน ;)
สำหรับรายละเอียดเพิ่มเติมว่าเหตุใดการซิงโครไนซ์ในฟิลด์ที่ไม่ใช่ขั้นสุดท้ายจึงแตกดูในกรณีทดสอบของฉัน: https://stackoverflow.com/a/21460055/2012947
และสำหรับรายละเอียดเพิ่มเติมว่าทำไมคุณต้องซิงโครไนซ์เลยเนื่องจาก RAM และแคชดูได้ที่นี่: https://stackoverflow.com/a/21409975/2012947
o
อ้างถึงในเวลาที่ถึงบล็อกซิงโครไนซ์ หากออบเจ็กต์ที่o
อ้างถึงการเปลี่ยนแปลงเธรดอื่นสามารถเข้ามาและดำเนินการบล็อกโค้ดที่ซิงโครไนซ์ได้