การตรวจสอบเธรดปลอดภัยหรือไม่


140

ฉันรู้ว่าการดำเนินการผสมเช่นi++ไม่ปลอดภัยตามที่พวกเขาเกี่ยวข้องกับการดำเนินงานหลาย ๆ

แต่การตรวจสอบการอ้างอิงด้วยตัวเองเป็นการดำเนินการที่ปลอดภัยของเธรดหรือไม่

a != a //is this thread-safe

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

แก้ไข:

public class TestThreadSafety {
    private Object a = new Object();

    public static void main(String[] args) {

        final TestThreadSafety instance = new TestThreadSafety();

        Thread testingReferenceThread = new Thread(new Runnable() {

            @Override
            public void run() {
                long countOfIterations = 0L;
                while(true){
                    boolean flag = instance.a != instance.a;
                    if(flag)
                        System.out.println(countOfIterations + ":" + flag);

                    countOfIterations++;
                }
            }
        });

        Thread updatingReferenceThread = new Thread(new Runnable() {

            @Override
            public void run() {
                while(true){
                    instance.a = new Object();
                }
            }
        });

        testingReferenceThread.start();
        updatingReferenceThread.start();
    }

}

นี่คือโปรแกรมที่ฉันใช้ในการทดสอบความปลอดภัยของเธรด

พฤติกรรมแปลก ๆ

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

ในขณะที่เอาต์พุตแนะนำหลังจากทำซ้ำ n (ไม่คงที่) เอาต์พุตดูเหมือนจะเป็นค่าคงที่และไม่เปลี่ยนแปลง

เอาท์พุท:

สำหรับการวนซ้ำ:

1494:true
1495:true
1496:true
19970:true
19972:true
19974:true
//after this there is not a single instance when the condition becomes true

2
"thread-safe" หมายความว่าอย่างไรในบริบทนี้ คุณกำลังถามว่ารับประกันได้หรือไม่ว่าคืนเท็จ
JB Nizet

@JBNizet ใช่ นั่นคือสิ่งที่ฉันคิด
Narendra Pathai

5
มันไม่ได้คืนค่าเท็จเสมอในบริบทแบบเธรดเดียว อาจเป็น NaN ..
279139 harold

4
คำอธิบายที่น่าจะเป็น: รหัสถูกคอมไพล์ในเวลาและโค้ดที่คอมไพล์โหลดตัวแปรเพียงครั้งเดียว สิ่งนี้คาดว่า
Marko Topolnik

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

คำตอบ:


124

ในกรณีที่ไม่มีการประสานรหัสนี้

Object a;

public boolean test() {
    return a != a;
}

trueอาจผลิต นี่คือ bytecode สำหรับtest()

    ALOAD 0
    GETFIELD test/Test1.a : Ljava/lang/Object;
    ALOAD 0
    GETFIELD test/Test1.a : Ljava/lang/Object;
    IF_ACMPEQ L1
...

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

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


22
แม้ว่าหลักฐานที่แข็งแกร่ง bytecode ไม่ได้พิสูจน์จริง มันจะต้องอยู่ที่ไหน
ซัก

10
@ Marko ฉันเห็นด้วยกับความคิดของคุณ แต่ไม่จำเป็นต้องเป็นข้อสรุปของคุณ สำหรับฉันแล้วโค้ดไบต์ข้างต้นเป็นวิธีการปฏิบัติที่ชัดเจน / เป็นที่ยอมรับ!=ซึ่งเกี่ยวข้องกับการโหลด LHS และ RHS แยกจากกัน ดังนั้นถ้า JLS ไม่พูดถึงสิ่งใดที่เฉพาะเจาะจงเกี่ยวกับการปรับให้เหมาะสมเมื่อ LHS และ RHS เหมือนกันในทาง syntactically กฎทั่วไปจะนำไปใช้ซึ่งหมายถึงการโหลดaสองครั้ง
Andrzej Doyle

20
ที่จริงแล้วสมมติว่า bytecode ที่สร้างนั้นสอดคล้องกับ JLS มันเป็นข้อพิสูจน์!
proskor

6
@ เอเดรีย: ประการแรก: แม้ว่าข้อสันนิษฐานนั้นจะไม่ถูกต้องการมีอยู่ของคอมไพเลอร์เดียวที่สามารถประเมินเป็น "ความจริง" ก็เพียงพอที่จะแสดงให้เห็นว่าบางครั้งมันสามารถประเมินเป็น "จริง" (แม้ว่าสเป็คจะห้าม ไม่ได้) ประการที่สอง: Java มีการระบุอย่างดีและคอมไพเลอร์ส่วนใหญ่สอดคล้องกับมันอย่างใกล้ชิด มันสมเหตุสมผลที่จะใช้พวกเขาเป็นข้อมูลอ้างอิงในส่วนนี้ ประการที่สาม: คุณใช้คำว่า "JRE" แต่ฉันไม่คิดว่ามันหมายถึงสิ่งที่คุณคิดว่ามันหมายถึง . .
ruakh

2
@AlexanderTorstling - "ฉันไม่แน่ใจว่าเพียงพอสำหรับการแยกการปรับให้เหมาะสมแบบอ่านครั้งเดียวออกไหม" มันไม่เพียงพอ ในความเป็นจริงในกรณีที่ไม่มีการประสาน (และพิเศษ "เกิดขึ้นก่อน" ความสัมพันธ์ที่กำหนด) การเพิ่มประสิทธิภาพที่ถูกต้อง
สตีเฟ่น C

47

ตรวจสอบa != aกระทู้ปลอดภัยหรือไม่

หากaสามารถปรับปรุงโดยเธรดอื่นได้ (โดยไม่ต้องซิงโครไนซ์!) ดังนั้นไม่

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

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

นี่หมายความว่า! = a สามารถกลับมาtrueได้

ใช่ในทางทฤษฎีภายใต้สถานการณ์บางอย่าง

หรือa != aอาจกลับมาfalseแม้ว่าจะaมีการเปลี่ยนแปลงพร้อมกัน


เกี่ยวกับ "พฤติกรรมแปลก ๆ ":

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

พฤติกรรม "แปลก" นี้สอดคล้องกับสถานการณ์การดำเนินการต่อไปนี้:

  1. โปรแกรมถูกโหลดและ JVM เริ่มตีความไบต์ เนื่องจาก (ตามที่เราเห็นจากเอาต์พุต javap) ไบต์จะทำการโหลดสองครั้งคุณ (เห็นได้ชัด) จะเห็นผลลัพธ์ของสภาพการแข่งขันเป็นครั้งคราว

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

  3. ตอนนี้สภาพการแข่งขันไม่ปรากฏอีกต่อไปเพราะไม่มีอีกสองโหลด

โปรดทราบว่าทั้งหมดนี้สอดคล้องกับสิ่งที่ JLS อนุญาตให้มีการนำ Java ไปใช้งาน


@kriss แสดงความคิดเห็นดังนี้:

ดูเหมือนว่าสิ่งนี้อาจเป็นสิ่งที่โปรแกรมเมอร์ C หรือ C ++ เรียกว่า "พฤติกรรมที่ไม่ได้กำหนด" (ขึ้นอยู่กับการนำไปใช้) ดูเหมือนว่าอาจมี UB ไม่กี่ในจาวาในกรณีมุมเช่นนี้

Java Memory Model (ที่ระบุในJLS 17.4 ) ระบุชุดของเงื่อนไขเบื้องต้นภายใต้การรับประกันหนึ่งเธรดเพื่อดูค่าหน่วยความจำที่เขียนโดยเธรดอื่น หากมีเธรดหนึ่งพยายามอ่านตัวแปรที่เขียนโดยอีกอันหนึ่งและเงื่อนไขเบื้องต้นเหล่านั้นไม่เป็นที่พอใจก็อาจมีการดำเนินการที่เป็นไปได้จำนวนหนึ่ง ... บางอันมีแนวโน้มที่จะไม่ถูกต้อง (จากมุมมองของข้อกำหนดของแอปพลิเคชัน) กล่าวอีกนัยหนึ่งชุดของพฤติกรรมที่เป็นไปได้ (เช่นชุดของ "การประหารชีวิตที่ดี") ถูกกำหนดไว้ แต่เราไม่สามารถพูดได้ว่าพฤติกรรมใดที่จะเกิดขึ้น

คอมไพเลอร์ได้รับอนุญาตให้รวมและจัดลำดับการโหลดใหม่และบันทึก (และทำสิ่งอื่น ๆ ) หากผลสุดท้ายของรหัสเหมือนกัน:

  • เมื่อดำเนินการโดยเธรดเดียวและ
  • เมื่อดำเนินการโดยเธรดอื่นที่ซิงโครไนซ์อย่างถูกต้อง

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


นี่หมายความว่าสิ่งนั้นa != aสามารถกลับมาจริงได้หรือไม่?
proskor

ฉันหมายถึงว่าบางทีในเครื่องของฉันฉันไม่สามารถจำลองได้ว่ารหัสข้างต้นไม่ปลอดภัยสำหรับเธรด ดังนั้นอาจมีเหตุผลทางทฤษฎีที่อยู่เบื้องหลัง
Narendra Pathai

@NarendraPathai - ไม่มีเหตุผลเชิงเหตุผลที่คุณไม่สามารถสาธิตได้ อาจมีเหตุผลที่เป็นไปได้ ... หรือบางทีคุณอาจไม่ได้โชคดี
สตีเฟ่นซี

โปรดตรวจสอบคำตอบที่อัปเดตของฉันกับโปรแกรมที่ฉันใช้ การตรวจสอบกลับคืนเป็นจริงในบางครั้ง แต่ดูเหมือนว่าจะมีพฤติกรรมแปลก ๆ ในผลลัพธ์
Narendra Pathai

1
@NarendraPathai - ดูคำอธิบายของฉัน
สตีเฟ่นซี

27

พิสูจน์ด้วย test-ng:

public class MyTest {

  private static Integer count=1;

  @Test(threadPoolSize = 1000, invocationCount=10000)
  public void test(){
    count = new Integer(new Random().nextInt());
    Assert.assertFalse(count != count);
  }

}

ฉันมี 2 ล้มเหลวในการร้องขอ 10,000 ดังนั้นไม่มันไม่ปลอดภัยเธรด


6
คุณไม่ได้ตรวจสอบความเท่าเทียมกัน ... Random.nextInt()ส่วนนั้นไม่จำเป็น คุณสามารถทดสอบด้วยnew Object()เช่นกัน
Marko Topolnik

@MarkoTopolnik โปรดตรวจสอบคำตอบที่อัปเดตของฉันกับโปรแกรมที่ฉันใช้ การตรวจสอบกลับคืนเป็นจริงในบางครั้ง แต่ดูเหมือนว่าจะมีพฤติกรรมแปลก ๆ ในผลลัพธ์
Narendra Pathai

1
หมายเหตุด้านวัตถุมักจะหมายถึงการสุ่มนำมาใช้ใหม่ไม่ได้สร้างขึ้นทุกครั้งที่คุณต้องการ int ใหม่
Simon Forsberg

15

ไม่มันไม่ใช่. สำหรับการเปรียบเทียบ Java VM ต้องวางค่าสองค่าเพื่อเปรียบเทียบกับสแต็กและรันคำสั่งเปรียบเทียบ (ซึ่งหนึ่งขึ้นอยู่กับชนิดของ "a")

Java VM อาจ:

  1. อ่าน "a" สองครั้งวางแต่ละอันลงบนสแต็กแล้วเปรียบเทียบผลลัพธ์
  2. อ่าน "a" เพียงครั้งเดียววางลงบนสแต็กทำซ้ำคำสั่ง ("dup") และเรียกใช้การเปรียบเทียบ
  3. กำจัดการแสดงออกอย่างสมบูรณ์และแทนที่ด้วย false

ในกรณีที่ 1 เธรดอื่นสามารถแก้ไขค่าสำหรับ "a" ระหว่างการอ่านทั้งสอง

กลยุทธ์ที่เลือกขึ้นอยู่กับคอมไพเลอร์ Java และ Java Runtime (โดยเฉพาะคอมไพเลอร์ JIT) มันอาจเปลี่ยนไปในระหว่างรันไทม์ของโปรแกรมของคุณ

หากคุณต้องการให้แน่ใจว่าวิธีการเข้าถึงตัวแปรนั้นคุณต้องทำvolatile(เรียกว่า "half memory barrier") หรือเพิ่ม barrier memory เต็ม ( synchronized) นอกจากนี้คุณยังสามารถใช้ API ระดับ hgiher (เช่นAtomicIntegerที่ Juned Ahasan พูดถึง)

สำหรับรายละเอียดเกี่ยวกับความปลอดภัยของเธรดให้อ่านJSR 133 ( โมเดลหน่วยความจำ Java )


การประกาศaว่าvolatileยังคงหมายถึงการอ่านที่แตกต่างกันสองรายการพร้อมความเป็นไปได้ของการเปลี่ยนแปลงในระหว่างนั้น
Holger

6

ทั้งหมดได้รับการอธิบายอย่างดีจาก Stephen C เพื่อความสนุกคุณสามารถลองเรียกใช้รหัสเดียวกันโดยใช้พารามิเตอร์ JVM ต่อไปนี้:

-XX:InlineSmallCode=0

สิ่งนี้ควรป้องกันการปรับให้เหมาะสมที่ทำโดย JIT (ทำบนเซิร์ฟเวอร์ฮอตสปอต 7) และคุณจะเห็นtrueตลอดไป (ฉันหยุดที่ 2,000,000 แต่ฉันคิดว่ามันจะดำเนินต่อไปหลังจากนั้น)

สำหรับข้อมูลด้านล่างคือรหัส JIT'ed ตามจริงแล้วฉันไม่ได้อ่านชุดประกอบอย่างคล่องแคล่วพอที่จะรู้ว่าการทดสอบนั้นเสร็จสิ้นจริงหรือโหลดมาจากไหน (บรรทัดที่ 26 คือการทดสอบflag = a != aและบรรทัดที่ 31 คือวงเล็บปิดของwhile(true))

  # {method} 'run' '()V' in 'javaapplication27/TestThreadSafety$1'
  0x00000000027dcc80: int3   
  0x00000000027dcc81: data32 data32 nop WORD PTR [rax+rax*1+0x0]
  0x00000000027dcc8c: data32 data32 xchg ax,ax
  0x00000000027dcc90: mov    DWORD PTR [rsp-0x6000],eax
  0x00000000027dcc97: push   rbp
  0x00000000027dcc98: sub    rsp,0x40
  0x00000000027dcc9c: mov    rbx,QWORD PTR [rdx+0x8]
  0x00000000027dcca0: mov    rbp,QWORD PTR [rdx+0x18]
  0x00000000027dcca4: mov    rcx,rdx
  0x00000000027dcca7: movabs r10,0x6e1a7680
  0x00000000027dccb1: call   r10
  0x00000000027dccb4: test   rbp,rbp
  0x00000000027dccb7: je     0x00000000027dccdd
  0x00000000027dccb9: mov    r10d,DWORD PTR [rbp+0x8]
  0x00000000027dccbd: cmp    r10d,0xefc158f4    ;   {oop('javaapplication27/TestThreadSafety$1')}
  0x00000000027dccc4: jne    0x00000000027dccf1
  0x00000000027dccc6: test   rbp,rbp
  0x00000000027dccc9: je     0x00000000027dcce1
  0x00000000027dcccb: cmp    r12d,DWORD PTR [rbp+0xc]
  0x00000000027dcccf: je     0x00000000027dcce1  ;*goto
                                                ; - javaapplication27.TestThreadSafety$1::run@62 (line 31)
  0x00000000027dccd1: add    rbx,0x1            ; OopMap{rbp=Oop off=85}
                                                ;*goto
                                                ; - javaapplication27.TestThreadSafety$1::run@62 (line 31)
  0x00000000027dccd5: test   DWORD PTR [rip+0xfffffffffdb53325],eax        # 0x0000000000330000
                                                ;*goto
                                                ; - javaapplication27.TestThreadSafety$1::run@62 (line 31)
                                                ;   {poll}
  0x00000000027dccdb: jmp    0x00000000027dccd1
  0x00000000027dccdd: xor    ebp,ebp
  0x00000000027dccdf: jmp    0x00000000027dccc6
  0x00000000027dcce1: mov    edx,0xffffff86
  0x00000000027dcce6: mov    QWORD PTR [rsp+0x20],rbx
  0x00000000027dcceb: call   0x00000000027a90a0  ; OopMap{rbp=Oop off=112}
                                                ;*aload_0
                                                ; - javaapplication27.TestThreadSafety$1::run@2 (line 26)
                                                ;   {runtime_call}
  0x00000000027dccf0: int3   
  0x00000000027dccf1: mov    edx,0xffffffad
  0x00000000027dccf6: mov    QWORD PTR [rsp+0x20],rbx
  0x00000000027dccfb: call   0x00000000027a90a0  ; OopMap{rbp=Oop off=128}
                                                ;*aload_0
                                                ; - javaapplication27.TestThreadSafety$1::run@2 (line 26)
                                                ;   {runtime_call}
  0x00000000027dcd00: int3                      ;*aload_0
                                                ; - javaapplication27.TestThreadSafety$1::run@2 (line 26)
  0x00000000027dcd01: int3   

1
นี่เป็นตัวอย่างที่ดีของประเภทของโค้ดที่ JVM จะสร้างขึ้นจริงเมื่อคุณมีการวนซ้ำไม่สิ้นสุดและทุกอย่างสามารถยกขึ้นหรือลงได้ ที่เกิดขึ้นจริง "ห่วง" ที่นี่มีสามคำแนะนำจากไป0x27dccd1 ในวงไม่มีเงื่อนไข (ตั้งแต่ห่วงเป็นอนันต์) อีกสองคำแนะนำในการวนซ้ำคือ- ซึ่งเป็นการเพิ่มขึ้น(แม้จะมีความจริงที่ว่าการวนซ้ำจะไม่ถูกออกดังนั้นค่านี้จะไม่ถูกอ่าน: อาจจำเป็นต้องใช้ในกรณีที่คุณเจาะเข้าไปในดีบักเกอร์), .. .0x27dccdfjmpadd rbc, 0x1countOfIterations
BeeOnRope

... และtestคำสั่งที่ดูแปลก ๆซึ่งจริง ๆ แล้วมีไว้สำหรับการเข้าถึงหน่วยความจำเท่านั้น (โปรดทราบว่าeaxไม่เคยมีการกำหนดไว้ในเมธอด!): เป็นหน้าพิเศษที่ไม่สามารถอ่านได้เมื่อ JVM ต้องการเรียกเธรดทั้งหมด เพื่อเข้าถึง Safepoint ดังนั้นจึงสามารถทำ gc หรือการดำเนินการอื่น ๆ ที่ต้องการให้เธรดทั้งหมดอยู่ในสถานะที่ทราบ
BeeOnRope

ยิ่งกว่านั้น JVM ยกการinstance. a != instance.aเปรียบเทียบออกจากลูปอย่างสมบูรณ์และดำเนินการเพียงครั้งเดียวก่อนที่จะเข้าสู่ลูป! มันรู้ว่ามันไม่จำเป็นต้องทำการรีโหลดinstanceหรือaเมื่อมันไม่ได้ประกาศความผันผวนและไม่มีรหัสอื่นที่สามารถเปลี่ยนมันได้ในเธรดเดียวกันดังนั้นมันจะถือว่าพวกมันเหมือนกันระหว่างลูปทั้งหมดซึ่งได้รับอนุญาตจากหน่วยความจำ แบบ
BeeOnRope

5

ไม่a != aไม่ปลอดภัยสำหรับเธรด สำนวนนี้ประกอบด้วยสามส่วน: โหลดaโหลดอีกครั้งและดำเนินการa !=เป็นไปได้สำหรับเธรดอื่นที่จะได้รับการล็อคอินทรินบนaของพาเรนต์และเปลี่ยนค่าของaระหว่างการดำเนินการโหลด 2

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

void method () {
    int a = 0;
    System.out.println(a != a);
}

ควรพิมพ์falseทุกครั้ง

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


ตัวอย่างของคุณsynchronizedไม่ถูกต้อง: เพื่อให้โค้ดนั้นรับประกันว่าจะพิมพ์falseวิธีการทั้งหมดที่ตั้งค่า aจะต้องเป็นsynchronizedเช่นกัน
ruakh

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

1
สถานที่ของคุณผิด คุณสามารถตั้งค่าฟิลด์ของวัตถุโดยไม่ได้รับการล็อคที่แท้จริง Java ไม่ต้องการเธรดเพื่อรับการล็อคที่แท้จริงของวัตถุก่อนการตั้งค่าเขตข้อมูล
ruakh

3

เกี่ยวกับพฤติกรรมแปลก ๆ :

เนื่องจากตัวแปรaไม่ได้ถูกทำเครื่องหมายว่าvolatileในบางจุดอาจมีค่าaอาจถูกแคชไว้โดยเธรด as ทั้งสองa != aนั้นเป็นเวอร์ชันที่แคชดังนั้นจึงเหมือนกันเสมอ (ความหมายflagคือตอนนี้เสมอfalse)


0

การอ่านที่เรียบง่ายไม่ได้เป็นแค่อะตอม หากaเป็นlongและไม่ได้ทำเครื่องหมายว่าเป็นvolatileJVM แบบ 32 บิตlong b = aจะไม่ปลอดภัยต่อเธรด


ระเหยและอะตอมมิกไม่เกี่ยวข้อง แม้ว่าฉันจะทำเครื่องหมายความผันผวนมันจะไม่ใช่อะตอม
Narendra Pathai

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