[java] ! = 점검 스레드는 안전한가요?

복합 작업 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



답변

동기화가없는 경우이 코드

Object a;

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

생산할 수 있습니다 true. 이것은 바이트 코드입니다test()

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

우리가 필드 a를 로컬 var에 두 번 로드하는 것을 볼 수 있듯이 a다른 스레드 비교에 의해 사이에 변경 된 경우 비 원자 연산 false입니다.

또한 메모리 가시성 문제는 여기서 관련 a이 있으므로 다른 스레드에서 변경 한 내용 이 현재 스레드에 표시 될 것이라는 보장은 없습니다 .


답변

점검은 a != a안전합니까?

a올바른 동기화없이 다른 스레드가 잠재적으로 업데이트 할 수있는 경우 아니요

이것을 프로그래밍하고 여러 스레드를 사용하려고했지만 실패하지 않았습니다. 내 컴퓨터에서 레이스를 시뮬레이션 할 수 없었습니다.

그것은 아무 의미가 없습니다! 문제는 a다른 스레드에 의해 업데이트 된 실행 이 JLS에 의해 허용 되는 경우 코드가 스레드로부터 안전하지 않다는 것입니다. 특정 시스템 및 특정 Java 구현에서 특정 테스트 케이스로 경쟁 조건을 발생시킬 수 없다고해서 다른 상황에서 발생하는 것을 막을 수는 없습니다.

! = A 반환 할 수있는 것을이 의미합니까 true.

이론적으로 특정 상황에서는 가능합니다.

또는 동시에 변경 되어도 a != a돌아올 수 있습니다.falsea


“이상한 행동”에 관하여 :

내 프로그램이 일부 반복 사이에서 시작되면 출력 플래그 값을 얻습니다. 즉, 동일한 참조에서 참조! = 검사가 실패 함을 의미합니다. 그러나 일부 반복 후 출력은 상수 값 false가되고 프로그램을 오랫동안 실행해도 단일 실제 출력이 생성되지 않습니다.

이 “이상한”동작은 다음 실행 시나리오와 일치합니다.

  1. 프로그램이로드되고 JVM이 바이트 코드 해석을 시작 합니다. (javap 출력에서 ​​볼 수 있듯이) 바이트 코드는 두 가지로드를 수행하기 때문에 경쟁 조건의 결과를 때때로 볼 수 있습니다.

  2. 얼마 후 코드는 JIT 컴파일러에 의해 컴파일됩니다. JIT 옵티마이 저는 동일한 메모리 슬롯 ( a) 의 두로드가 서로 가까이 있음 을 확인하고 두 번째로드를 최적화합니다. (실제로 테스트를 완전히 최적화 할 가능성이 있습니다 …)

  3. 이제 더 이상 두 개의로드가 없기 때문에 경쟁 조건이 더 이상 나타나지 않습니다.

이것은 모두 JLS가 Java 구현을 허용하는 것과 일치합니다.


@kriss는 다음과 같이 논평했다.

이것은 C 또는 C ++ 프로그래머가 “정의되지 않은 동작”(구현에 따라 다름)이라고 부르는 것 같습니다. Java와 같은 코너 경우에는 몇 가지 UB가있는 것처럼 보입니다.

Java 메모리 모델 ( JLS 17.4로 지정)은 한 스레드가 다른 스레드가 쓴 메모리 값을 볼 수 있도록하는 일련의 사전 조건을 지정합니다. 하나의 스레드가 다른 스레드에 의해 작성된 변수를 읽으려고 시도하고 해당 전제 조건이 충족되지 않으면 여러 실행이 가능할 수 있습니다 (일부 응용 프로그램 요구 사항의 관점에서) 부정확 할 수 있습니다. 즉, 설정 가능한 행동은 정의 ( “잘 구성된 실행”세트를 즉),하지만 우리는 발생하는 행동의 어느 말할 수 없습니다.

컴파일러는 코드의 최종 효과가 동일한 경우로드를 결합 및 재정렬하고 저장하고 다른 작업을 수행 할 수 있습니다.

  • 단일 스레드로 실행될 때
  • 메모리 모델에 따라 올바르게 동기화되는 다른 스레드에서 실행될 때.

그러나 코드가 제대로 동기화되지 않아서 “이전에 발생하는”관계가 제대로 구성된 실행 세트를 충분히 제한하지 않는 경우 컴파일러는 “잘못된”결과를 제공하는 방식으로로드 및 저장 순서를 다시 지정할 수 있습니다. (그러나 그것은 단지 프로그램이 틀렸다는 것을 말하는 것입니다.)


답변

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);
  }

}

10 000 호출에서 2 개의 실패가 있습니다. 따라서 NO , 스레드 안전 하지 않습니다.


답변

전혀 그렇지 않다. 비교를 위해 Java VM은 스택에서 비교할 두 값을 넣고 비교 명령 ( “a”의 유형에 따라 다름)을 실행해야합니다.

Java VM은 다음을 수행 할 수 있습니다.

  1. “a”를 두 번 읽고 각각 스택에 놓고 결과를 비교하십시오.
  2. “a”를 한 번만 읽고 스택에 놓고 복제 ( “dup”명령) 한 다음 비교를 실행하십시오.
  3. 식을 완전히 제거하고 false

첫 번째 경우에는 다른 스레드가 두 읽기 사이의 “a”값을 수정할 수 있습니다.

어떤 전략을 선택할지는 Java 컴파일러 및 Java 런타임 (특히 JIT 컴파일러)에 따라 다릅니다. 프로그램 실행 중에 변경 될 수도 있습니다.

변수에 액세스하는 방법을 확인하려면 변수를 만들거나 volatile( “반쯤 메모리 장벽”이라고 함) 전체 메모리 장벽을 추가해야합니다 ( synchronized). 일부 hgiher 레벨 API를 사용할 수도 있습니다 (예 : AtomicIntegerJuned Ahasan에서 언급 한대로).

스레드 안전성에 대한 자세한 내용은 JSR 133 ( Java Memory Model )을 읽으십시오 .


답변

Stephen C에 의해 모두 잘 설명되어 있습니다. 재미를 위해 다음 JVM 매개 변수를 사용하여 동일한 코드를 실행 해 볼 수 있습니다.

-XX:InlineSmallCode=0

이것은 JIT (핫스팟 7 서버에서 수행)가 수행하는 최적화를 방해해야하며 true영원히 볼 수 있습니다 (2,000,000에서 멈췄지만 그 후에도 계속한다고 가정합니다).

자세한 내용은 JIT 코드입니다. 솔직히 말해서, 테스트가 실제로 수행되는지 또는 두로드가 어디서 오는지를 알기에 유창하게 어셈블리를 읽지 않습니다. (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   


답변

아니요, a != a스레드 안전하지 않습니다. 이 표현식은 load 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캐시 할 수 있으며, 다른 스레드의 변화가 캐시 된 값에 영향을 미치지 않습니다.


답변

이상한 행동에 대해 :

변수 a가로 표시되지 않았기 때문에 volatile어느 시점 a에서 스레드에 의해 값 이 캐시 될 수 있습니다. 모두 a의의는 a != a다음 캐시 된 버전 및 따라서 항상 같은 (의미는 flag항상 지금이다 false).