2009-06-17 2 views
37

자바 원시 정수 (int)는 전혀 중요하지 않습니까? int를 공유하는 두 개의 스레드를 사용하는 일부 실험은 이라는 것을 나타내지 만 물론 이 아닌이라는 증거가 없다는 것을 의미하지는 않습니다.자바 원시적 인 int는 의도적으로 또는 우연히 원자입니까?

특히, 내가 실행 된 검사이 있었다 :

public class IntSafeChecker { 
    static int thing; 
    static boolean keepWatching = true; 

    // Watcher just looks for monotonically increasing values 
    static class Watcher extends Thread { 
     public void run() { 
      boolean hasBefore = false; 
      int thingBefore = 0; 

      while(keepWatching) { 
       // observe the shared int 
       int thingNow = thing; 
       // fake the 1st value to keep test happy 
       if(hasBefore == false) { 
        thingBefore = thingNow; 
        hasBefore = true; 
       } 
       // check for decreases (due to partially written values) 
       if(thingNow < thingBefore) { 
        System.err.println("MAJOR TROUBLE!"); 
       } 
       thingBefore = thingNow; 
      } 
     } 
    } 

    // Modifier just counts the shared int up to 1 billion 
    static class Modifier extends Thread { 
     public void run() { 
      int what = 0; 
      for(int i = 0; i < 1000000000; ++i) { 
       what += 1; 
       thing = what; 
      } 
      // kill the watcher when done 
      keepWatching = false; 
     } 
    } 

    public static void main(String[] args) { 
     Modifier m = new Modifier(); 
     Watcher w = new Watcher(); 
     m.start(); 
     w.start(); 
    } 
} 

를 (그리고 단지 32 비트 윈도우 PC에 자바 JRE 1.6.0_07으로 시도되었다)

본질적으로, 수정은 카운트 순서를 기록 관찰자는 관측 된 값이 결코 감소하지 않는지를 확인하면서 공유 된 정수에 더한다. 32 비트 값을 4 개의 개별 바이트 (또는 심지어 2 개의 16 비트 워드)로 액세스해야하는 시스템에서는 Watcher가 일관성이없는 반 갱신 상태에서 공유 정수를 잡을 확률이 줄어들어 값이 감소합니다 증가하기보다는 오히려. 이는 (가설적인) 데이터 바이트가 LSB 1 또는 MSB 1로 수집되거나 기록되는지 여부에 따라 작동해야하지만 최상의 경우에만 probablistic입니다.

Java 사양에서 필요하지 않더라도 32 비트 값이 효과적으로 원 자성을 가질 수 있다는 오늘날의 광범위한 데이터 경로를 고려할 때 매우 가능성이 높습니다. 사실, 32 비트 데이터 버스를 사용하면 원자 접근을 얻기 위해 32 비트 정수보다 바이트까지 열심히 노력해야 할 수도 있습니다.

"java primitive thread safety"를 검색하면 스레드 안전 클래스 및 객체에 많은 양이 표시되지만 기본 요소에 대한 정보를 찾는 것은 건초 더미에서 속담 바늘을 찾는 것처럼 보입니다.

+1

AtomicInteger 클래스가 있는지 궁금합니다. (http://java.sun.com/javase/6/docs/api/java/util/concurrent/atomic /AtomicInteger.html), 다음. – Joey

+3

@Johannes Rössel - AtomicInteger에는 손상 가능성이없는 것보다 많은 정보가 있습니다. 가시성, CAS 기반 작업. –

+1

그래, 나는 어떤면에서 AtomicInteger가 VolatileInteger로 명명되어야한다고 주장한다. –

답변

54

longdouble을 제외하고는 기본적으로 원자이다 ( 원자 수 있지만 될 필요가 없습니다). 을 매우으로 명확하게 정직하지는 않지만 분명히 암시합니다.JLS의 section 17.4.3 가입일

:

순차적 일관성 실행 내에는 위에 전체 순서가 모든 개별적인 작업 의 순서와 일치한다 ( 를 읽기 및 쓰기 등) 프로그램이고 각각의 개별 작업은 원자 단위이며 모든 스레드가 즉시 볼 수있는 입니다.

다음 17.7의 : 일부 구현 찾을 수 있습니다

그것을 인접한 32 비트에 두 쓰기 동작으로 64 비트 길이 또는 더블 값에 단일 쓰기 조치를 분할하는 것이 편리 값. 효율성면에서이 동작은 구체화 입니다. Java 가상 머신 기계는 길이가 길어서 원자 값이 두 값을 직접적으로 쓰거나 을 두 부분으로 씁니다.

원자력은 변동성과 매우 다릅니다.

한 스레드가 정수를 5로 업데이트하면 다른 스레드가 1 또는 4 또는 다른 중간 상태를 볼 수는 없지만 다른 명시 적 변동이나 잠금이없는 경우 다른 스레드는 0을 영원히 볼 수 있습니다.

바이트에 대한 원자 적 액세스를 얻으려고 열심히 노력하는 것과 관련하여, 당신이 맞습니다. VM은 열심히 노력해야 할 수도 있지만, 그렇습니다. section 17.6 사양 :

일부 프로세서는 단일 바이트 쓰기 기능을 제공하지 않습니다. 단지, 전체 단어를 읽어 해당 바이트를 갱신하고, 다음 메모리에 다시 전체 단어를 작성 에 의해 바이트에게 이러한 프로세서 배열 업데이트를 구현하는 것은 불법이 될 것입니다. 이 문제는 때로는 단어 찢어짐으로 알려진 이며 프로세서에서는 쉽게 바이트를 개별적으로 업데이트 할 수 없으므로 다른 접근 방식이 필요합니다.

즉, 올바르게하려면 JVM에 달려 있습니다.

+0

... JLS에서 '매우 명확하지 않다'는 것에 동의했다 : 17.4.3의 첫 번째 인용문은 순차적 인 메모리 모델에 쓰는 것은 JVM에서 사용되지 않는다. (작은 각주를 보라). – Ayrat

+0

위와 같은 경우 AtomicInteger 클래스의 요점은 무엇입니까? –

+0

@BradEllis : 두 가지 : 첫째, 원자 적이라는 것은 값에 대한 변경이 즉시 표시된다는 것을 의미하지는 않지만, 'AtomicInteger'는이를 제공합니다. 둘째로,'AtomicInteger'는 값을 원자 적으로 수정하는 옵션을 제공합니다 - 반면에'x + = 2; '와 같은 것은 원자 적 연산이 아닙니다. 'x'가 0의 값으로 시작하면, 하나의 쓰레드는'x + = 1;'을 수행하고 다른 쓰레드는'x + = 2;'를 수행하고,'x'의 마지막 결과는 1,2,3 일 수 있습니다. AtomicInteger', 동치 연산은 * 항상 * 3이됩니다. –

23
  • 스레드 안전을 증명할 수있는 시험 아무리 - 그것은 단지 을 반증 할 수 그것;
  • 는 I 일부 구현 편리 인접하는 32 개 비트 값에 두 개의 기록 동작으로 64 비트 길이 또는 큰 값의 단일 기입 동작을 분할 찾을 수

어떤 상태 JLS 17.7의 간접 참조를 발견 .

과 Java 프로그램 언어의 메모리 모델의 목적을 위해 더 아래

는 비 휘발성 긴 또는 더블 값에 대한 단일 쓰기는 두 개의 분리 된 쓰기로 처리됩니다 하나 각 32에 비트 절반.

이것은 int에 대한 쓰기가 원자적임을 의미합니다.

+12

+1 "테스트의 양은 스레드 안전성을 증명할 수 없으며이를 반증 할 수 있습니다." – dfa

+5

@dfa - 그게 내가 의미하는 바는 "그들이 없다는 증거가 없다는 것"은 – JustJeff

0

이것은 다소 복잡하며 시스템 wordsize와 관련이 있습니다. 브루스 에켈 (Bruce Eckel)은 더 자세히 설명합니다 : Java Threads. 모든 메모리 자바에 액세스

3

정수 또는 더 작은 유형의 읽기 또는 쓰기는 원자 적이어야하지만 Robert는 지적했듯이 long 및 double은 구현에 따라 달라질 수도 있고 그렇지 않을 수도 있습니다. 그러나 모든 증가 연산자를 포함하여 읽기 및 쓰기를 모두 사용하는 모든 연산은 원자 적이 아닙니다. 따라서 정수 i = 0에서 연산을 수행해야한다면 i ++와 i = 10이 있고 결과는 1, 10 또는 11이 될 수 있습니다.

이와 같은 연산의 경우, AtomicInteger에는 값을 원자 적으로 수정하는 동안 또는 값을 원자 적으로 증가시키는 방법이 있습니다.

마지막으로 스레드는 변수 값을 캐시 할 수 있으며 다른 스레드에서 변경 한 내용을 볼 수 없습니다.두 스레드가 항상 다른 스레드가 변경 한 사항을 확인하려면 해당 변수를 volatile로 표시해야합니다.

4

나는 Jon Skeet에 동의하며, 종종 용어가 서로 바꿔서 사용되기 때문에 원 자성, 변동성 및 스레드 안전성에 대한 개념을 혼란스럽게한다는 점을 강조하고 싶습니다.
예를 들어, 다음 사항을 고려하십시오 누군가가이 작업이 원자이라고 주장 수도 있지만

private static int i = 0; 
public void increment() { i++; } 

는, 참조 된 가설이 잘못된 것입니다.
1)
이 2)
이 3)

따라서,이 변수에서 작동 스레드는 다음과 같이 동기화되어야 쓰기 업데이트 읽기 :

private static int i = 0; 
private static final Object LOCK = new Object(); 
public void increment() { 
    synchronized(LOCK) { 
     i++; 
    } 
} 

i++; 세 가지 작업을 수행

또는이 :

private static int i = 0; 
public static synchronized void increment() { 
    i++; 
} 

단일 객체 인스턴스의 경우 다중 스레드가 액세스하고 공유 가능한 데이터를 처리하는 메소드를 호출 할 때 메소드의 매개 변수, 지역 변수 및 반환 값이 각 스레드. 이 도움이
http://www.javamex.com/tutorials/synchronization_volatile.shtml

희망 :

자세한 내용은이 링크를 확인하십시오.

업데이트 : 클래스 개체 자체에서 동기화 할 수있는 경우도 있습니다. 여기에 더 많은 정보 : How to synchronize a static variable among threads running different instances of a class in java?

+0

설명하려고하는 것은 괜찮지 만 원시 값으로는 동기화 할 수 없습니다. –

+0

@SotiriosDelimanolis 머리를 주셔서 감사합니다! 나는 그것이 지금 ok 다라고 생각한다! :) – Kounavi

+1

마지막 예제에서 필드는 정적이지만 동기화 된 메서드가 아닙니다. 즉, 동기화가 인스턴스별로 발생한다는 것을 의미합니다. 즉, 서로 다른 스레드가 서로 다른 개체 인스턴스를 사용하면 액세스가 불가능합니다. 스레드간에 동기화 (https://stackoverflow.com/a/6214251/699700 참조) –

4

은 당신이 예상대로 작동 나던 생각 : 다른 객체에 모든 시간을 sychronizing되도록

private static int i = 0; 
public void increment() { 
    synchronized (i) { 
     i++; 
    } 
} 

정수는 불변이다. int "i"가 Integer 객체에 자동 저장되고 잠금을 설정합니다. 다른 스레드가이 메소드에 들어가면 int는 다른 Integer 객체로 자동 보봇 (autobox)되고 다른 객체에 lock을 설정합니다.

0

원자 읽기 및 쓰기는 읽지 않을 것입니다. int의 최초의 16 비트는 낡은 값으로부터의 갱신입니다.

이것은 다른 스레드가이 글을 볼 때 WHEN에 대해서는 아무런 의미가 없습니다.

짧은 이야기는 두 스레드가 메모리 장벽없이 경쟁 할 때 무언가가 손실된다는 것입니다.

하나의 공유 정수를 증가시키고 자신의 증분을 세는 두 개 이상의 스레드를 스핀 업합니다. 정수가 어떤 값 (INT_MAX, 예를 들어 니스와 큰 것)으로되면 모든 것을 멈추고 int의 값과 각 스레드가 수행 한 증가분의 수를 반환합니다.여기

import java.util.Stack; 

public class Test{ 

    static int ctr = Integer.MIN_VALUE; 
    final static int THREADS = 4; 

    private static void runone(){ 
    ctr = 0; 
    Stack<Thread> threads = new Stack<>(); 
    for(int i = 0; i < THREADS; i++){ 
     Thread t = new Thread(new Runnable(){ 
     long cycles = 0; 

     @Override 
     public void run(){ 
      while(ctr != Integer.MAX_VALUE){ 
      ctr++; 
      cycles++; 
      } 
      System.out.println("Cycles: " + cycles + ", ctr: " + ctr); 
     } 
     }); 
     t.start(); 
     threads.push(t); 
    } 
    while(!threads.isEmpty()) 
     try{ 
     threads.pop().join(); 
     }catch(InterruptedException e){ 
     // TODO Auto-generated catch block 
     e.printStackTrace(); 
     } 
    System.out.println(); 
    } 

    public static void main(String args[]){ 
    System.out.println("Int Range: " + ((long) Integer.MAX_VALUE - (long) Integer.MIN_VALUE)); 
    System.out.println(" Int Max: " + Integer.MAX_VALUE); 
    System.out.println(); 
    for(;;) 
     runone(); 
    } 
} 

내 쿼드 코어 상자에이 시험의 결과 (코드에서 스레드 수 놀 주시기 바랍니다, 난 그냥 분명, 내 코어 수를 일치) :

Int Range: 4294967295 
Int Max: 2147483647 

Cycles: 2145700893, ctr: 76261202 
Cycles: 2147479716, ctr: 1825148133 
Cycles: 2146138184, ctr: 1078605849 
Cycles: 2147282173, ctr: 2147483647 

Cycles: 2147421893, ctr: 127333260 
Cycles: 2146759053, ctr: 220350845 
Cycles: 2146742845, ctr: 450438551 
Cycles: 2146537691, ctr: 2147483647 

Cycles: 2110149932, ctr: 696604594 
Cycles: 2146769437, ctr: 2147483647 
Cycles: 2147095646, ctr: 2147483647 
Cycles: 2147483647, ctr: 2147483647 

Cycles: 2147483647, ctr: 330141890 
Cycles: 2145029662, ctr: 2147483647 
Cycles: 2143136845, ctr: 2147483647 
Cycles: 2147007903, ctr: 2147483647 

Cycles: 2147483647, ctr: 197621458 
Cycles: 2076982910, ctr: 2147483647 
Cycles: 2125642094, ctr: 2147483647 
Cycles: 2125321197, ctr: 2147483647 

Cycles: 2132759837, ctr: 330963474 
Cycles: 2102475117, ctr: 2147483647 
Cycles: 2147390638, ctr: 2147483647 
Cycles: 2147483647, ctr: 2147483647 
0

이를 원자 없다 : 그러나

i++; 

이있다 :

i = 5; 

여기가 약간의 혼동이있는 곳이라고 생각합니다.