Programing

Java 5+의 휘발성이 다른 스레드의 가시성을 보장하지 않는 이유는 무엇입니까?

lottogame 2020. 11. 6. 07:45
반응형

Java 5+의 휘발성이 다른 스레드의 가시성을 보장하지 않는 이유는 무엇입니까?


에 따르면 :

http://www.ibm.com/developerworks/library/j-jtp03304/

새로운 메모리 모델에서는 스레드 A가 휘발성 변수 V에 쓰고 스레드 B가 V에서 읽을 때 V가 쓰여졌을 때 A가 볼 수 있었던 모든 변수 값이 이제 B에 표시됩니다.

그리고 인터넷의 많은 곳에서 다음 코드가 "오류"를 출력해서는 안된다고 말합니다.

public class Test {
    volatile static private int a;
    static private int b;

    public static void main(String [] args) throws Exception {
        for (int i = 0; i < 100; i++) {
            new Thread() {

                @Override
                public void run() {
                    int tt = b; // makes the jvm cache the value of b

                    while (a==0) {

                    }

                    if (b == 0) {
                        System.out.println("error");
                    }
                }

            }.start();
        }

        b = 1;
        a = 1;
    }
}

b 1 인 경우 모든 스레드에 대해 1 이어야 합니다 a.

그러나 가끔 "오류"가 인쇄 됩니다. 이것이 어떻게 가능한지?


최신 정보:

관심있는 사람을 위해이 버그는 Java 7u6 빌드 b14에서 해결 및 수정되었습니다. 여기에서 버그 보고서 / 수정 사항을 볼 수 있습니다.

원래 답변

기억 가시성 / 순서의 관점에서 생각할 때 당신은 그것의 발생 전 관계에 대해 생각해야 할 것입니다. 에 대한 중요한 사전 조건 b != 0은입니다 a == 1. 그렇다면 a != 1b는 0 또는 1이 될 수 있습니다.

쓰레드가 본다면 a == 1그 쓰레드는 b == 1.

OP 예제에서 Java 5 이후, 일단 while(a == 0)브레이크 아웃 b는 1이 보장됩니다.

편집하다:

시뮬레이션을 여러 번 실행했지만 결과를 보지 못했습니다.

어떤 OS, Java 버전 및 CPU에서 테스트하고 있습니까?

Windows 7, Java 1.6_24를 사용 중입니다 (_31로 시도 중).

편집 2 :

OP 및 Walter Laan에 대한 찬사-64 비트 Java에서 32 비트 Java로 전환했을 때만 64 비트 Windows 7을 사용 (제외되지 않을 수 있음) 할 때만 발생했습니다.

편집 3 :

에 대한 할당 tt또는 오히려 staticget은 b상당한 영향을 미치는 것 같습니다 (이를 증명하기 위해를 제거하고 int tt = b;항상 작동해야합니다.

하중의 표시 b로를 tt다음 coniditonal 경우에 (그 값에 대한 참조를하지 사용되는 로컬 필드에 저장한다 tt). 따라서 b == 0true이면 로컬 저장소 tt가 0 임을 의미 할 수 있습니다 (이 시점에서 1을 local에 할당하는 경쟁입니다 tt). 이것은 클라이언트가 설정된 32 비트 Java 1.6 및 7에서만 해당되는 것 같습니다.

두 출력 어셈블리를 비교했는데 즉각적인 차이점이 여기에있었습니다. (이들은 스 니펫임을 명심하십시오).

이것은 "오류"를 인쇄했습니다

 0x021dd753: test   %eax,0x180100      ;   {poll}
  0x021dd759: cmp    $0x0,%ecx
  0x021dd75c: je     0x021dd748         ;*ifeq
                                        ; - Test$1::run@7 (line 13)
  0x021dd75e: cmp    $0x0,%edx
  0x021dd761: jne    0x021dd788         ;*ifne
                                        ; - Test$1::run@13 (line 17)
  0x021dd767: nop    
  0x021dd768: jmp    0x021dd7b8         ;   {no_reloc}
  0x021dd76d: xchg   %ax,%ax
  0x021dd770: jmp    0x021dd7d2         ; implicit exception: dispatches to 0x021dd7c2
  0x021dd775: nop                       ;*getstatic out
                                        ; - Test$1::run@16 (line 18)
  0x021dd776: cmp    (%ecx),%eax        ; implicit exception: dispatches to 0x021dd7dc
  0x021dd778: mov    $0x39239500,%edx   ;*invokevirtual println

"오류"가 인쇄되지 않았습니다.

0x0226d763: test   %eax,0x180100      ;   {poll}
  0x0226d769: cmp    $0x0,%edx
  0x0226d76c: je     0x0226d758         ;*ifeq
                                        ; - Test$1::run@7 (line 13)
  0x0226d76e: mov    $0x341b77f8,%edx   ;   {oop('Test')}
  0x0226d773: mov    0x154(%edx),%edx   ;*getstatic b
                                        ; - Test::access$0@0 (line 3)
                                        ; - Test$1::run@10 (line 17)
  0x0226d779: cmp    $0x0,%edx
  0x0226d77c: jne    0x0226d7a8         ;*ifne
                                        ; - Test$1::run@13 (line 17)
  0x0226d782: nopw   0x0(%eax,%eax,1)
  0x0226d788: jmp    0x0226d7ed         ;   {no_reloc}
  0x0226d78d: xchg   %ax,%ax
  0x0226d790: jmp    0x0226d807         ; implicit exception: dispatches to 0x0226d7f7
  0x0226d795: nop                       ;*getstatic out
                                        ; - Test$1::run@16 (line 18)
  0x0226d796: cmp    (%ecx),%eax        ; implicit exception: dispatches to 0x0226d811
  0x0226d798: mov    $0x39239500,%edx   ;*invokevirtual println

In this example the first entry is from a run that printed "error" while the second was from one which didnt.

It seems that the working run loaded and assigned b correctly before testing it equal to 0.

  0x0226d76e: mov    $0x341b77f8,%edx   ;   {oop('Test')}
  0x0226d773: mov    0x154(%edx),%edx   ;*getstatic b
                                        ; - Test::access$0@0 (line 3)
                                        ; - Test$1::run@10 (line 17)
  0x0226d779: cmp    $0x0,%edx
  0x0226d77c: jne    0x0226d7a8         ;*ifne
                                        ; - Test$1::run@13 (line 17)

While the run that printed "error" loaded the cached version of %edx

  0x021dd75e: cmp    $0x0,%edx
  0x021dd761: jne    0x021dd788         ;*ifne
                                        ; - Test$1::run@13 (line 17)

For those who have more experience with assembler please weigh in :)

Edit 4

Should be my last edit, as the concurrency dev's get a hand on it, I did test with and without the int tt = b; assignment some more. I found that when I increase the max from 100 to 1000 there seems to be a 100% error rate when int tt = b is included and a 0% chance when it is excluded.


Based on the extract from JCiP below, I would have thought that your example should never print "error":

The visibility effects of volatile variables extend beyond the value of the volatile variable itself. When a thread A writes to a volatile variable and subsequently thread B reads that same variable, the values of all variables that were visible to A prior to writing to the volatile variable become visible to B after reading the volatile variable.


You might want to check out a discussion thread on the concurrency interest mailing list on this question: http://cs.oswego.edu/pipermail/concurrency-interest/2012-May/009440.html

It seems like the problem is more easily reproduced with the client JVM (-client).


in My opinion,The Problem acurred due to Lack of Synchronization :

NOTICE : if b=1 heppens before a=1, and a is volatile while b is not, then b=1 actually updates for all threads only after a=1 is finished (according to the quate's logic).

what heppend in your code is that b=1 was first updated for the main process only, then only when the volatile assignment finished, all the threads b's updated. I think that maybe assignments of volatile are not working as atomic operations (needs to point far, and somehow update rest of refernces to act like volatiles) so this would be my guess why one thread read b=0 instead of b=1.

Consider this change to the code, that shows my claim:

public class Test {
    volatile static private int a;
    static private int b;
    private static Object lock = new Object();


    public static void main(String [] args) throws Exception {
        for (int i = 0; i < 100; i++) {
            new Thread() {

                @Override
                public void run() {
                    int tt = b; // makes the jvm cache the value of b

                    while (true) {
                        synchronized (lock ) {
                            if (a!=0) break;
                         }
                    }

                    if (b == 0) {
                        System.out.println("error");
                    }
                }

            }.start();
        }
        b = 1;
        synchronized (lock ) {
        a = 1;
        }  
    }
}

참고URL : https://stackoverflow.com/questions/10620680/why-doesnt-volatile-in-java-5-ensure-visibility-from-another-thread

반응형