답변:
두 가지 주요 용도가 있습니다 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()
증분 대신 임의 계산 ( )을 수행 하고 반환하기 전에 결과를 처리합니다.
read
와 write that value + 1
작업 사이의 카운터를 수정하는 경우 이전 업데이트를 덮어 쓰지 않고 ( "업데이트 손실"문제를 피함)이를 감지 할 수 있습니다. compareAndSet
이전의 값이 2
실제로 클래스 인 경우 실제로 호출 하는 특수한 경우입니다. compareAndSet(2, 3)
따라서 다른 스레드가 값을 수정 한 경우 증가 방법이 처음부터 효과적으로 다시 시작됩니다.
내가 생각할 수있는 가장 간단한 예는 원자 연산을 증가시키는 것입니다.
표준 정수로 :
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++
기억할 때 (또는 미리) 올바른 모니터 세트를 얻는 것보다 훨씬 어렵습니다 .
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
의 주요 사용은 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
예를 들어 Map
s에서 값 으로 변경 가능한 버전으로 사용할 수 있습니다 .
AtomicInteger
기본 키 를 사용하기 를 원한다고 생각하지 않습니다 . 기본 equals()
구현을 사용하기 때문에 의미가 맵에서 사용되는 것으로 예상되지 않습니다.
예를 들어, 어떤 클래스의 인스턴스를 생성하는 라이브러리가 있습니다. 이러한 인스턴스는 서버로 전송되는 명령을 나타내며 각 명령에는 고유 한 ID가 있어야하므로 각 인스턴스에는 고유 한 정수 ID가 있어야합니다. 여러 스레드가 동시에 명령을 보낼 수 있으므로 AtomicInteger를 사용하여 해당 ID를 생성합니다. 다른 방법은 일종의 잠금 및 일반 정수를 사용하는 것이지만 느리고 우아하지는 않습니다.
Java 8에서 원자 클래스는 두 가지 흥미로운 기능으로 확장되었습니다.
둘 다 원자 값의 업데이트를 수행하기 위해 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 에서 가져온 것입니다 .
여러 스레드에서 액세스하거나 만들 수있는 객체에 ID를 제공해야 할 때 일반적으로 AtomicInteger를 사용하며 일반적으로 객체의 생성자에서 액세스하는 클래스의 정적 속성으로 사용합니다.
원자 정수 또는 long에서 compareAndSwap (CAS)을 사용하여 비 차단 잠금을 구현할 수 있습니다. "TL2"소프트웨어 트랜잭션 메모리 논문이 설명합니다 :
모든 버전의 메모리 위치와 특수한 버전의 쓰기 잠금을 연결합니다. 가장 간단한 형식으로, 버전이 지정된 쓰기 잠금은 CAS 작업을 사용하여 잠금을 획득하고이를 해제 할 저장소를 사용하는 단일 단어 스핀 록입니다. 하나의 비트 만 있으면 잠금이 해제되었음을 나타내므로 나머지 잠금 단어를 사용하여 버전 번호를 유지합니다.
그것이 설명하는 것은 먼저 원자 정수를 읽는 것입니다. 이것을 무시 된 잠금 비트와 버전 번호로 나눕니다. CAS는 현재 버전 번호와 함께 잠금 비트 세트와 다음 버전 번호로 잠금 비트가 지워지도록 시도합니다. 성공하고 자물쇠를 소유 한 실이 될 때까지 반복하십시오. 잠금 비트가 해제 된 상태에서 현재 버전 번호를 설정하여 잠금을 해제하십시오. 이 백서는 스레드에서 쓰일 때 일관된 읽기 세트를 갖도록 조정하기 위해 잠금의 버전 번호를 사용하는 방법을 설명합니다.
이 기사에서는 프로세서가 비교 및 스왑 작업을 매우 효율적으로 수행 할 수 있도록 하드웨어를 지원한다고 설명합니다. 또한 다음과 같이 주장합니다.
원자 변수를 사용하는 비 차단 CAS 기반 카운터는 낮거나 중간 정도의 경합에서 잠금 기반 카운터보다 성능이 우수합니다.
나는 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 문제는 프로세스가 작업을 계속하기 위해 리소스를 필요로하는 것처럼 리소스에 대한 액세스 제어가 필요한 경우, 즉 포크가 필요할 때 사용됩니다.
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 값이 업데이트되지 않았습니다