2012-11-15 1 views
10

자바 사양 보장 (, 반대로. long 더블 types에 대한관계는 원시 변수 할당은 항상 원자이다

을 기대 유명한 i++ 증가 조작에 대응 Fetch-and-Add 작업은 비 원자 것 이 코드를 가정 읽기 - 수정 - 쓰기 작업으로 이어지는 때문에

:.

public void assign(int b) { 
    int a = b; 
} 

생성 바이트 코드는 다음

public void assign(int); 
    Code: 
     0: iload_1  
     1: istore_2  
     2: return 

따라서, 우리는 할당 두 단계 (로드 및 저장) 구성된다 참조. 말했듯이, X86 프로세서 수 (현대 것들 적어도), 원자 적으로 증가 동작을 동작하는 것을 알고

public void assign(int); 
    Code: 
     0: iload_1  
     1: iinc   1, 1 //extra step here regarding the previous sample 
     4: istore_2  
     5: return 

:

public void assign(int b) { 
     int i = b++; 
} 

바이트 코드 :

이 코드를 가정

컴퓨터 과학에서 fetch-and-add CPU 명령은 원자 적으로 수정하는 특수한 명령입니다 메모리의 내용은 위치입니다. 이것은 상호 배제를 구현하고 다중 프로세서 시스템에서 동시적인 세마포어 알고리즘을 구현하는 데 사용됩니다. 따라서

, 첫 번째 질문 : 바이트 코드는 두 단계 (로드 및 저장)를 필요로한다는 사실에도 불구하고, 자바는 할당 작업이 항상 원자 어떤 프로세서의 아키텍처 때문에 수행 작업이라는 사실에 의존 않습니다 그것의 명세에서 영구적 인 원 자성 (primitive assignment)을 보장 할 수 있는가?

두 번째 질문 :i++ 작업 (또는 AtomicInteger)를 동기화 전혀 필요가 없습니다, 매우 현대적인 X86 프로세서 및 다른 아키텍처를 통해 컴파일 된 코드를 공유하지 않고 사항을 확인하는 것은 잘못인가? 그것은 이미 원자 적이라고 생각합니다.

+1

내 이해는 할당의 원 자성은 istore가 원자 적이라는 것을 의미합니다. 즉, 'a = b'에서는'b '가 읽힌 다음 새로운 값으로 변할 가능성이 있습니다. 'a'에 할당됩니다. 그러나 원자 성은 'a'가 'b'가 보유한 두 개의 값을 섞어서는 안된다는 것을 보장합니다. – assylias

+0

@assylias 그게 내가 생각하기에, 나는이 관점에 동의한다. – Mik378

답변

4

두 번째 질문을 고려하십시오.

i++은 사실이 아닌 X86 Fetch-And-Add 명령어로 변환됩니다. 코드는 JVM에 의해 컴파일 및 최적화 경우는 (다음 사항을 확인하기 위해 JVM의 소스 코드를 확인해야 할 것입니다) 사실 일 수도 있지만, 그 코드는 또한 을 가져오고 를 추가 해석 모드에서 실행할 수 있습니다 분리되어 있고 동기화되지 않았습니다.

public class Main { 
    volatile int a; 

    static public final void main (String[] args) throws Exception { 
    new Main().run(); 
    } 

    private void run() { 
     for (int i = 0; i < 1000000; i++) { 
     increase(); 
     } 
    } 

    private void increase() { 
    a++; 
    } 
} 

내가 JVM의 Java HotSpot(TM) Server VM (17.0-b12-fastdebug) for windows-x86 JRE (1.6.0_20-ea-fastdebug-b02), built on Apr 1 2010 03:25:33 버전 (내 드라이브에 어딘가에 가지고이 일을) 사용 :

는 호기심 나는이 자바 코드를 생성 것을 어셈블리 코드 확인.우선

가이 컴파일된다 :

00c  PUSHL EBP 
    SUB ESP,8 # Create frame 
013  MOV EBX,[ECX + #8] # int ! Field VolatileMain.a 
016  MEMBAR-acquire ! (empty encoding) 
016  MEMBAR-release ! (empty encoding) 
016  INC EBX 
017  MOV [ECX + #8],EBX ! Field VolatileMain.a 
01a  MEMBAR-volatile (unnecessary so empty encoding) 
01a  LOCK ADDL [ESP + #0], 0 ! membar_volatile 
01f  ADD ESP,8 # Destroy frame 
    POPL EBP 
    TEST PollPage,EAX ! Poll Safepoint 

029  RET 
이어서

은 이것으로 인라인 컴파일된다

0a8 B11: # B11 B12 &lt;- B10 B11 Loop: B11-B11 inner stride: not constant post of N161 Freq: 0.999997 
0a8  MOV EBX,[ESI] # int ! Field VolatileMain.a 
0aa  MEMBAR-acquire ! (empty encoding) 
0aa  MEMBAR-release ! (empty encoding) 
0aa  INC EDI 
0ab  INC EBX 
0ac  MOV [ESI],EBX ! Field VolatileMain.a 
0ae  MEMBAR-volatile (unnecessary so empty encoding) 
0ae  LOCK ADDL [ESP + #0], 0 ! membar_volatile 
0b3  CMP EDI,#1000000 
0b9  Jl,s B11 # Loop end P=0.500000 C=126282.000000 

이 그것을 실행 중요한 출력 (java -server -XX:+PrintAssembly -cp . Main)입니다

보시다시피 a++에 대한 가져 오기 및 추가 명령은 사용하지 않습니다.

+0

+1 스펙이 원자 연산을 보장하지 않는다고해서 하나가 사용되지 않는다는 것을 의미하지는 않는다. BTW : 위의 예제에서 JIT는 메서드를 아무 것도 최적화하지 않을 것이라고 가정합니다. ;) –

+0

@ShyJ 훌륭한 샘플, 감사합니다! – Mik378

1

첫 번째 질문에 대해 : 읽기 및 쓰기는 원자 적이지만 읽기/쓰기 작업은 아닙니다. 나는 프리미티브의 특정 참조를 찾을 수 없습니다하지만 JLS #17.7 뭔가 비슷한에 대한 참조를 말한다 :

는에 기록과 관계없이 32 비트 또는 64 비트 값으로 구현 여부, 항상 원자 있습니다 참조 읽습니다.

따라서 iload 및 istore는 모두 원자이지만 전체 (iload, istore) 연산은 그렇지 않습니다.

i ++ 작업을 동기화 할 필요가 전혀 없다고 생각합니까? 두 번째 질문에 대해서는

코드는 아래 ==> 제대로 심지어 프로세서 아키텍처에 ++ 작업을 동기화 할 몇 가지 ++ 번역에 열중하고 있었다 보여줍니다 내 86 기계 (그리고 1000)에 982를 인쇄 이는 fetch-and-add 명령어를 지원합니다.

public class Test1 { 

    private static int i = 0; 

    public static void main(String args[]) throws InterruptedException { 
     ExecutorService executor = Executors.newFixedThreadPool(10); 
     final CountDownLatch start = new CountDownLatch(1); 
     final Set<Integer> set = new ConcurrentSkipListSet<>(); 
     Runnable r = new Runnable() { 
      @Override 
      public void run() { 
       try { 
        start.await(); 
       } catch (InterruptedException ignore) {} 
       for (int j = 0; j < 100; j++) { 
        set.add(i++); 
       } 
      } 
     }; 

     for (int j = 0; j < 10; j++) { 
      executor.submit(r); 
     } 
     start.countDown(); 
     executor.shutdown(); 
     executor.awaitTermination(1, TimeUnit.SECONDS); 
     System.out.println(set.size()); 
    } 
} 
+1

그것은 ShyJ가 주장한 것을 확인합니다.> ""나는 ++가 X86 Fetch-And-Add 명령을 사실이 아닌 것으로 해석 함을 의미합니다. " – Mik378

+0

@ Mik378이 첫 번째 질문에 뭔가를 추가했습니다. – assylias

+0

고마워, 지금은 분명해. :) – Mik378

5

의 I는 ++를 x86로 번역 할 경우에도 추가 가져 오기 - 그리고-명령에 했나요 메모리가에 CPU의 로컬 메모리 registres을 의미하지 때문에 아무 것도 변경하지 않을 것이다 명령을 추가 가져 오기 - 및 - 장치/응용 프로그램의 일반 메모리 최신 CPU에서이 속성은 CPU의 로컬 메모리 캐시까지 확장되며 멀티 코어 CPU의 경우 다른 코어에서 사용되는 다양한 캐시로 확장 될 수 있지만 멀티 스레딩 응용 프로그램의 경우에는 확장 될 수 있습니다. 이 배포본이 쓰레드가 사용하는 메모리의 복사본으로 확장된다는 보장은 전혀 없습니다.

다중 스레드 응용 프로그램에서 변수가 동시에 실행되는 다른 스레드에 의해 수정 될 수 있으면 시스템에서 제공하는 동기화 메카니즘을 사용해야하며 명령 i ++가 명령을 사용한다는 사실에 의존 할 수 없습니다 원자 코드로 된 단일 코드.