AtomicInteger의 실제 사용


228

나는 AtomicInteger와 다른 원자 변수가 동시 액세스를 허용한다는 것을 이해합니다. 이 클래스는 어떤 경우에 일반적으로 사용됩니까?

답변:


189

두 가지 주요 용도가 있습니다 AtomicInteger.

  • incrementAndGet()많은 스레드에서 동시에 사용할 수 있는 원자 카운터 ( 등)

  • 비 차단 알고리즘을 구현하기 위해 비교 및 스왑 명령 ( compareAndSet())을 지원하는 프리미티브입니다 .

    다음은 Brian Göetz의 Java Concurrency In Practice 의 비 차단 난수 생성기 예제입니다 .

    public class AtomicPseudoRandom extends PseudoRandom {
        private AtomicInteger seed;
        AtomicPseudoRandom(int seed) {
            this.seed = new AtomicInteger(seed);
        }
    
        public int nextInt(int n) {
            while (true) {
                int s = seed.get();
                int nextSeed = calculateNext(s);
                if (seed.compareAndSet(s, nextSeed)) {
                    int remainder = s % n;
                    return remainder > 0 ? remainder : remainder + n;
                }
            }
        }
        ...
    }

    보시다시피 기본적으로와 거의 같은 방식으로 작동 incrementAndGet()하지만 calculateNext()증분 대신 임의 계산 ( )을 수행 하고 반환하기 전에 결과를 처리합니다.


1
나는 처음 사용을 이해한다고 생각합니다. 이는 속성에 다시 액세스하기 전에 카운터가 증가했는지 확인하기위한 것입니다. 옳은? 두 번째 사용에 대한 간단한 예를 들어 주시겠습니까?
James P.

8
첫 번째 사용에 대한 이해는 일종의 사실입니다. 다른 스레드가 readwrite that value + 1작업 사이의 카운터를 수정하는 경우 이전 업데이트를 덮어 쓰지 않고 ( "업데이트 손실"문제를 피함)이를 감지 할 수 있습니다. compareAndSet이전의 값이 2실제로 클래스 인 경우 실제로 호출 하는 특수한 경우입니다. compareAndSet(2, 3)따라서 다른 스레드가 값을 수정 한 경우 증가 방법이 처음부터 효과적으로 다시 시작됩니다.
Andrzej Doyle

3
"나머지> 0? 나머지 : 나머지 + n;" 이 식에서 n이 0 일 때 나머지를 n에 더할 이유가 있습니까?
sandeepkunkunuru

100

내가 생각할 수있는 가장 간단한 예는 원자 연산을 증가시키는 것입니다.

표준 정수로 :

private volatile int counter;

public int getNextUniqueIndex() {
    return counter++; // Not atomic, multiple threads could get the same result
}

AtomicInteger로 :

private AtomicInteger counter;

public int getNextUniqueIndex() {
    return counter.getAndIncrement();
}

후자는 모든 액세스 동기화에 의존하지 않고 간단한 돌연변이 효과 (특히 계산 또는 고유 색인)를 수행하는 매우 간단한 방법입니다.

더 복잡한 동기화가없는 로직을 사용하여 사용할 수있다 compareAndSet()낙관적 잠금의 유형으로 -이에 따라 현재 값, 계산 결과를 얻을,이 결과를 설정 IFF에 값이 여전히 다른, 계산을 다시 시작하는 데 사용되는 입력 -하지만, 계산 예제는 매우 유용하며 AtomicIntegers여러 스레드가 관련되어 있다는 힌트가 있으면 계산 및 VM 전체 고유 생성기에 자주 사용 됩니다. 왜냐하면 작업하기가 너무 쉽기 때문에 평범한 사용을 조기에 최적화하는 것으로 간주합니다. ints.

거의 항상 같은 선언 ints과 적절한 synchronized선언으로 동일한 동기화 보장을 얻을 수 있지만 AtomicInteger스레드 안전성은 모든 메소드의 가능한 인터리빙 및 모니터에 대해 걱정할 필요없이 실제 객체 자체에 내장되어 있다는 것입니다. 그 int값 에 액세스합니다 . 전화를 걸 때 실수로 스레드 안전을 위반하여 getAndIncrement()돌아올 때나 i++기억할 때 (또는 미리) 올바른 모니터 세트를 얻는 것보다 훨씬 어렵습니다 .


2
이 명확한 설명에 감사드립니다. 메소드가 모두 동기화 된 클래스에 비해 AtomicInteger를 사용하면 어떤 이점이 있습니까? 후자가 "무거운"것으로 간주됩니까?
James P.

3
내 관점에서 볼 때 그것은 주로 AtomicIntegers로 얻는 캡슐화입니다. 동기화는 필요한 것에서 정확히 이루어지며 공개 API에있는 설명적인 메소드를 통해 의도 한 결과가 무엇인지 설명합니다. (또한 어느 정도 당신이 옳다면, 종종 너무 조잡한 클래스의 모든 메소드를 동기화하는 경우가 종종 있지만 HotSpot은 잠금 최적화 및 조기 최적화에 대한 규칙을 수행하지만 가독성은
Andrzej Doyle

이것은 매우 명확하고 정확한 설명입니다, 감사합니다 !!
Akash5288

마지막으로 설명이 제대로 정리되었습니다.
베니 보 테마

58

AtomicInteger의 메소드를 보면 int의 일반적인 작업에 해당하는 경향이 있음을 알 수 있습니다. 예를 들어 :

static AtomicInteger i;

// Later, in a thread
int current = i.incrementAndGet();

이 스레드 안전 버전입니다.

static int i;

// Later, in a thread
int current = ++i;

방법은 다음과 같이지도 :
++i되어 i.incrementAndGet()
i++있습니다 i.getAndIncrement()
--i입니다 i.decrementAndGet()
i--입니다 i.getAndDecrement()
i = x입니다 i.set(x)
x = i입니다x = i.get()

compareAndSet또는 같은 다른 편리한 방법이 있습니다addAndGet


37

의 주요 사용은 AtomicInteger다중 스레드 상황에 있고 당신이 사용하지 않고 정수에 스레드 안전 작업을 수행해야 할 때입니다 synchronized. 기본 유형에 대한 지정 및 검색 int은 이미 원 자성이지만 AtomicInteger원 자성이 아닌 많은 연산이 제공됩니다 int.

가장 간단한 것은 getAndXXX또는 xXXAndGet입니다. 예를 들어 getAndIncrement(), i++실제로는 검색, 추가 및 할당이라는 세 가지 작업에 대한 단축이기 때문에 원자가 아닌 원자에 해당합니다 . compareAndSet세마포어, 잠금 장치, 래치 등을 구현하는 데 매우 유용합니다.

를 사용하면 AtomicInteger동기화를 사용하여 동일한 것을 수행하는 것보다 빠르고 읽기 쉽습니다.

간단한 테스트 :

public synchronized int incrementNotAtomic() {
    return notAtomic++;
}

public void performTestNotAtomic() {
    final long start = System.currentTimeMillis();
    for (int i = 0 ; i < NUM ; i++) {
        incrementNotAtomic();
    }
    System.out.println("Not atomic: "+(System.currentTimeMillis() - start));
}

public void performTestAtomic() {
    final long start = System.currentTimeMillis();
    for (int i = 0 ; i < NUM ; i++) {
        atomic.getAndIncrement();
    }
    System.out.println("Atomic: "+(System.currentTimeMillis() - start));
}

Java 1.6이 설치된 PC에서 원자 테스트는 3 초 안에 실행되는 반면 동기화 된 테스트는 약 5.5 초 안에 실행됩니다. 여기서 문제는 동기화 작업 ( notAtomic++)이 실제로 짧다는 것입니다. 따라서 동기화 비용은 작업과 비교하여 실제로 중요합니다.

원 자성 외에 AtomicInteger는 Integer예를 들어 Maps에서 값 으로 변경 가능한 버전으로 사용할 수 있습니다 .


1
AtomicInteger기본 키 를 사용하기 를 원한다고 생각하지 않습니다 . 기본 equals()구현을 사용하기 때문에 의미가 맵에서 사용되는 것으로 예상되지 않습니다.
Andrzej Doyle

1
@Andrzej는 변경이 불가능한 키가 아니라 값입니다.
gabuzo

@gabuzo 원자 정수가 동기화보다 잘 수행되는 이유가 무엇입니까?
Supun Wijerathne

이 테스트는 현재 (6 년 이상) 매우 오래되어 최근 JRE로 다시 테스트하는 것이 흥미로울 수 있습니다. 나는 AtomicInteger에서 대답하기에 충분히 깊이 들어 가지 않았지만 이것은 매우 구체적인 작업 이므로이 특정 경우에만 작동하는 동기화 기술을 사용합니다. 또한이 테스트는 모노 스레드 방식으로 구현되고 있으며로드가 많은 환경에서 유사한 테스트를 수행하면 AtomicInteger에게는 분명한 승리를주지 못할 수도 있습니다.
gabuzo

나는 3ms와
5.5ms를

17

예를 들어, 어떤 클래스의 인스턴스를 생성하는 라이브러리가 있습니다. 이러한 인스턴스는 서버로 전송되는 명령을 나타내며 각 명령에는 고유 한 ID가 있어야하므로 각 인스턴스에는 고유 한 정수 ID가 있어야합니다. 여러 스레드가 동시에 명령을 보낼 수 있으므로 AtomicInteger를 사용하여 해당 ID를 생성합니다. 다른 방법은 일종의 잠금 및 일반 정수를 사용하는 것이지만 느리고 우아하지는 않습니다.


이 실제 사례를 공유해 주셔서 감사합니다. 내 프로그램으로 가져 오는 각 파일에 대해 고유 ID가 필요하기 때문에 사용해야 할 것 같습니다. :)
James P.

7

gabuzo가 말했듯이 때로는 참조로 int를 전달하려고 할 때 AtomicIntegers를 사용합니다. 아키텍처 특정 코드가있는 내장 클래스이므로 신속하게 코딩 할 수있는 MutableInteger보다 쉽고 최적화되어 있습니다. 그것은 수업을 학대하는 느낌입니다.


7

Java 8에서 원자 클래스는 두 가지 흥미로운 기능으로 확장되었습니다.

  • int getAndUpdate (IntUnaryOperator updateFunction)
  • int updateAndGet (IntUnaryOperator updateFunction)

둘 다 원자 값의 업데이트를 수행하기 위해 updateFunction을 사용하고 있습니다. 차이점은 첫 번째 값은 이전 값을 반환하고 두 번째 값은 새 값을 반환한다는 것입니다. updateFunction은 표준보다 더 복잡한 "비교 및 설정"작업을 수행하도록 구현 될 수 있습니다. 예를 들어 원자 카운터가 0 아래로 떨어지지 않는지 확인할 수 있습니다. 일반적으로 동기화가 필요하며 여기에 코드에 잠금이 없습니다.

    public class Counter {

      private final AtomicInteger number;

      public Counter(int number) {
        this.number = new AtomicInteger(number);
      }

      /** @return true if still can decrease */
      public boolean dec() {
        // updateAndGet(fn) executed atomically:
        return number.updateAndGet(n -> (n > 0) ? n - 1 : n) > 0;
      }
    }

코드는 Java Atomic Example 에서 가져온 것입니다 .


5

여러 스레드에서 액세스하거나 만들 수있는 객체에 ID를 제공해야 할 때 일반적으로 AtomicInteger를 사용하며 일반적으로 객체의 생성자에서 액세스하는 클래스의 정적 속성으로 사용합니다.


4

원자 정수 또는 long에서 compareAndSwap (CAS)을 사용하여 비 차단 잠금을 구현할 수 있습니다. "TL2"소프트웨어 트랜잭션 메모리 논문이 설명합니다 :

모든 버전의 메모리 위치와 특수한 버전의 쓰기 잠금을 연결합니다. 가장 간단한 형식으로, 버전이 지정된 쓰기 잠금은 CAS 작업을 사용하여 잠금을 획득하고이를 해제 할 저장소를 사용하는 단일 단어 스핀 록입니다. 하나의 비트 만 있으면 잠금이 해제되었음을 나타내므로 나머지 잠금 단어를 사용하여 버전 번호를 유지합니다.

그것이 설명하는 것은 먼저 원자 정수를 읽는 것입니다. 이것을 무시 된 잠금 비트와 버전 번호로 나눕니다. CAS는 현재 버전 번호와 함께 잠금 비트 세트와 다음 버전 번호로 잠금 비트가 지워지도록 시도합니다. 성공하고 자물쇠를 소유 한 실이 될 때까지 반복하십시오. 잠금 비트가 해제 된 상태에서 현재 버전 번호를 설정하여 잠금을 해제하십시오. 이 백서는 스레드에서 쓰일 때 일관된 읽기 세트를 갖도록 조정하기 위해 잠금의 버전 번호를 사용하는 방법을 설명합니다.

이 기사에서는 프로세서가 비교 및 ​​스왑 작업을 매우 효율적으로 수행 할 수 있도록 하드웨어를 지원한다고 설명합니다. 또한 다음과 같이 주장합니다.

원자 변수를 사용하는 비 차단 CAS 기반 카운터는 낮거나 중간 정도의 경합에서 잠금 기반 카운터보다 성능이 우수합니다.


3

핵심은 동시에 액세스하고 수정할 수 있다는 것입니다. 그것들은 일반적으로 멀티 스레드 환경에서 카운터로 사용됩니다. 소개 전에 이것은 다양한 방법을 동기화 된 블록으로 묶은 사용자 작성 클래스 여야했습니다.


내가 참조. 속성 또는 인스턴스가 응용 프로그램 내에서 일종의 전역 변수 역할을하는 경우에 해당됩니다. 아니면 다른 생각을 할 수 있습니까?
James P.

1

나는 AtomicInteger를 사용하여 Dining Philosopher의 문제를 해결했습니다.

내 솔루션에서 AtomicInteger 인스턴스는 포크를 나타내는 데 사용되었으며 철학자마다 두 가지가 필요합니다. 각 철학자는 1에서 5까지의 정수로 식별됩니다. 철학자가 포크를 사용할 때 AtomicInteger는 철학자 1에서 5까지의 값을 보유하고, 그렇지 않으면 포크는 사용되지 않으므로 AtomicInteger의 값은 -1입니다. .

그런 다음 AtomicInteger를 사용하면 한 번의 원자 조작으로 포크가 비어 있는지, 값 ==-1인지 확인하고, 비어 있으면 포크의 소유자로 설정할 수 있습니다. 아래 코드를 참조하십시오.

AtomicInteger fork0 = neededForks[0];//neededForks is an array that holds the forks needed per Philosopher
AtomicInteger fork1 = neededForks[1];
while(true){    
    if (Hungry) {
        //if fork is free (==-1) then grab it by denoting who took it
        if (!fork0.compareAndSet(-1, p) || !fork1.compareAndSet(-1, p)) {
          //at least one fork was not succesfully grabbed, release both and try again later
            fork0.compareAndSet(p, -1);
            fork1.compareAndSet(p, -1);
            try {
                synchronized (lock) {//sleep and get notified later when a philosopher puts down one fork                    
                    lock.wait();//try again later, goes back up the loop
                }
            } catch (InterruptedException e) {}

        } else {
            //sucessfully grabbed both forks
            transition(fork_l_free_and_fork_r_free);
        }
    }
}

compareAndSet 메소드는 차단되지 않으므로 처리량을 늘리고 더 많은 작업을 수행해야합니다. 아시다시피, Dining Philosophers 문제는 프로세스가 작업을 계속하기 위해 리소스를 필요로하는 것처럼 리소스에 대한 액세스 제어가 필요한 경우, 즉 포크가 필요할 때 사용됩니다.


0

compareAndSet () 함수의 간단한 예 :

import java.util.concurrent.atomic.AtomicInteger; 

public class GFG { 
    public static void main(String args[]) 
    { 

        // Initially value as 0 
        AtomicInteger val = new AtomicInteger(0); 

        // Prints the updated value 
        System.out.println("Previous value: "
                           + val); 

        // Checks if previous value was 0 
        // and then updates it 
        boolean res = val.compareAndSet(0, 6); 

        // Checks if the value was updated. 
        if (res) 
            System.out.println("The value was"
                               + " updated and it is "
                           + val); 
        else
            System.out.println("The value was "
                               + "not updated"); 
      } 
  } 

인쇄 된 값은 다음과 같습니다. 이전 값 : 0 값이 업데이트되었으며 6입니다. 또 다른 간단한 예는 다음과 같습니다.

    import java.util.concurrent.atomic.AtomicInteger; 

public class GFG { 
    public static void main(String args[]) 
    { 

        // Initially value as 0 
        AtomicInteger val 
            = new AtomicInteger(0); 

        // Prints the updated value 
        System.out.println("Previous value: "
                           + val); 

         // Checks if previous value was 0 
        // and then updates it 
        boolean res = val.compareAndSet(10, 6); 

          // Checks if the value was updated. 
          if (res) 
            System.out.println("The value was"
                               + " updated and it is "
                               + val); 
        else
            System.out.println("The value was "
                               + "not updated"); 
    } 
} 

인쇄 된 값 : 이전 값 : 0 값이 업데이트되지 않았습니다

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.