Java Language
원자 유형
수색…
소개
Java Atomic Types는 잠금에 의존하지 않고 스레드로부터 안전하고 원자적인 기본 조작을 제공하는 단순 변경 가능한 유형입니다. 잠금은 동시성 병목 현상이 발생하거나 교착 상태 또는 라이브 록의 위험이있는 경우에 사용하기위한 것입니다.
매개 변수
매개 변수 | 기술 |
---|---|
세트 | 휘발성 필드 세트 |
도망 | 필드의 휘발성 읽기 |
게으른 집합 | 이것은 상점 주문 상점 작동입니다 |
compareAndSet | 값이 expeed 값이면 새 값으로 보냅니다. |
getAndSet | 현재 값을 얻고 업데이트 |
비고
본질적으로 휘발성 읽기 또는 쓰기 및 CAS 작업의 조합에 관한 것입니다. 이를 이해하는 가장 좋은 방법은 소스 코드를 직접 보는 것입니다. 예 : AtomicInteger , Unsafe.getAndSet
원자 유형 생성하기
간단한 멀티 스레드 코드의 경우 동기화를 사용할 수 있습니다. 그러나 동기화를 사용하면 활력에 영향을 미치며 코드베이스가 복잡해지면 교착 상태 , 기아 상태 또는 Livelock으로 끝날 가능성이 높아집니다.
보다 복잡한 동시성의 경우에는 원자 변수를 사용하는 것이 더 나은 대안입니다. 이는 동기화 된 메서드 또는 코드 블록을 사용하는 오버 헤드없이 개별 변수를 스레드로부터 안전한 방식으로 액세스 할 수 있기 때문입니다.
AtomicInteger
유형 만들기 :
AtomicInteger aInt = new AtomicInteger() // Create with default value 0
AtomicInteger aInt = new AtomicInteger(1) // Create with initial value 1
다른 인스턴스 유형과 유사합니다.
AtomicIntegerArray aIntArray = new AtomicIntegerArray(10) // Create array of specific length
AtomicIntegerArray aIntArray = new AtomicIntegerArray(new int[] {1, 2, 3}) // Initialize array with another array
다른 원자 유형과 유사합니다.
float
및 double
유형이 없다는 주목할만한 예외가 있습니다. 이들은의 사용을 통해 시뮬레이션 할 수 있습니다 Float.floatToIntBits(float)
과 Float.intBitsToFloat(int)
위한 float
뿐만 아니라 Double.doubleToLongBits(double)
와 Double.longBitsToDouble(long)
복식.
당신이 사용하고자하는 경우 sun.misc.Unsafe
당신의 원자 연산을 사용하여 원자와 같은 원시적 인 변수를 사용할 수 있습니다 sun.misc.Unsafe
. 모든 원시 타입은 int 또는 longs로 변환되거나 인코딩되어야합니다. 자세한 내용은 sun.misc.Unsafe를 참조하십시오.
원자 유형에 대한 동기 부여
멀티 스레드 응용 프로그램을 구현하는 간단한 방법은 Java의 기본 제공 동기화 및 잠금 프리미티브를 사용하는 것입니다. 예 : synchronized
키워드. 다음 예는 synchronized
를 사용하여 카운트를 누적하는 방법을 보여줍니다.
public class Counters {
private final int[] counters;
public Counters(int nosCounters) {
counters = new int[nosCounters];
}
/**
* Increments the integer at the given index
*/
public synchronized void count(int number) {
if (number >= 0 && number < counters.length) {
counters[number]++;
}
}
/**
* Obtains the current count of the number at the given index,
* or if there is no number at that index, returns 0.
*/
public synchronized int getCount(int number) {
return (number >= 0 && number < counters.length) ? counters[number] : 0;
}
}
이 구현은 올바르게 작동합니다. 그러나 많은 수의 스레드가 동일한 Counters
개체에서 동시에 많은 호출을 수행하는 경우 병목 현상이 발생할 수 있습니다. 구체적으로 :
- 각
synchronized
메서드 호출은 현재 스레드가Counters
인스턴스에 대한 잠금을 획득하면서 시작합니다. - 스레드는
number
값을 확인하고 카운터를 업데이트하는 동안 잠금을 유지합니다. - 마지막으로 잠금을 해제하여 다른 스레드가 액세스 할 수있게합니다.
한 스레드가 다른 스레드가 보유하고있는 동안 한 스레드가 잠금 획득을 시도하면 시도중인 스레드는 잠금이 해제 될 때까지 1 단계에서 차단 (중지)됩니다. 여러 스레드가 대기 중이면 그 중 하나가 스레드를 가져오고 다른 스레드는 계속 차단됩니다.
이로 인해 몇 가지 문제가 발생할 수 있습니다.
자물쇠에 대한 많은 논쟁 이있는 경우 (즉, 많은 스레드가이를 획득하려고 시도하는 경우), 일부 스레드는 오랫동안 차단 될 수 있습니다.
스레드가 잠금 대기를 차단하면 운영 체제는 일반적으로 다른 스레드로 스위치 실행을 시도합니다. 이 컨텍스트 전환 은 프로세서에 상대적으로 큰 성능 영향을줍니다.
같은 락 상에 복수의 thread가 블록되어있는 경우, 어느 쪽인가가 "공정하게"취급되는 (즉, 각 thread가 실행되도록 (듯이) 예약되고있다) 것이 보증되지 않습니다. 이로 인해 스레드 기아가 발생할 수 있습니다.
원자형을 어떻게 구현하나요?
먼저 AtomicInteger
카운터를 사용하여 위의 예제를 다시 AtomicInteger
보겠습니다.
public class Counters {
private final AtomicInteger[] counters;
public Counters(int nosCounters) {
counters = new AtomicInteger[nosCounters];
for (int i = 0; i < nosCounters; i++) {
counters[i] = new AtomicInteger();
}
}
/**
* Increments the integer at the given index
*/
public void count(int number) {
if (number >= 0 && number < counters.length) {
counters[number].incrementAndGet();
}
}
/**
* Obtains the current count of the object at the given index,
* or if there is no number at that index, returns 0.
*/
public int getCount(int number) {
return (number >= 0 && number < counters.length) ?
counters[number].get() : 0;
}
}
int[]
를 AtomicInteger[]
로 대체하고 각 요소의 인스턴스로 초기화했습니다. 또한 int
값의 연산 대신 incrementAndGet()
및 get()
에 get()
호출을 추가했습니다.
그러나 가장 중요한 것은 잠금이 더 이상 필요 없기 때문에 synchronized
키워드를 제거 할 수 있다는 것입니다. 이것은 incrementAndGet()
및 get()
조작이 기본 적이고 스레드로부터 안전 하기 때문에 작동 합니다 . 이 문맥에서는 다음을 의미합니다.
배열의 각 카운터는 작업의 "이전"상태 ( "증가"와 같은) 또는 "후"상태에서만 관찰 할 수 있습니다.
작업이 시간
T
에서 발생한다고 가정하면, 스레드는 시간T
이후의 "이전"상태를 볼 수 없습니다.
게다가, 2 개의 thread가 실제로 같은 AtomicInteger
인스턴스를 동시에 갱신하려고하면 (자), 오퍼레이션의 구현에서는, 지정된 인스턴스로 한 번에 1 회의 증분 (increment)만이 발생하는 것을 보증합니다. 이것은 잠금없이 이루어 지므로 종종 성능이 향상됩니다.
원자 유형은 어떻게 작동합니까?
원자 유형은 일반적으로 대상 기계의 명령어 세트에있는 특수 하드웨어 명령어에 의존합니다. 예를 들어, Intel 기반 명령어 세트는 특정 순서의 메모리 연산을 원자 적으로 수행하는 CAS
( Compare and Swap ) 명령어를 제공합니다.
이러한 저수준 명령어는 각 AtomicXxx
클래스의 API에서 상위 레벨 연산을 구현하는 데 사용됩니다. 예를 들어 (다시 C 유사 의사 코드에서) :
private volatile num;
int increment() {
while (TRUE) {
int old = num;
int new = old + 1;
if (old == compare_and_swap(&num, old, new)) {
return new;
}
}
}
AtomicXxxx
에 경합이 없으면 if
테스트가 성공하고 루프가 즉시 종료됩니다. 경합이있는 if
스레드 중 하나를 제외한 모든 스레드에서 if
가 실패하고 루프의 작은 사이클 동안 루프에서 "회전"합니다. 실제로, 회전 속도는 수십 배 더 빠릅니다 (CAS 작업이 실패 할 경우 다시 시도하면 경쟁이 더 늘어날 수 있기 때문에 비동기식으로 높은 수준의 경합을 제외하고는 동기화가 원자 클래스보다 성능이 우수함) 스레드를 일시 중단하고 다른 스레드로 전환하는 것보다 하나.
덧붙여 말하자면, CAS 명령은 일반적으로 JVM에서 비경쟁 잠금 을 구현하는 데 사용됩니다. JVM이 현재 잠금이 잠겨져 있지 않다는 것을 알게되면 CAS를 사용하여 잠금을 획득하려고 시도합니다. CAS가 성공하면 비싼 스레드 스케줄링, 컨텍스트 스위칭 등을 할 필요가 없습니다. 사용 된 기술에 대한 자세한 내용 은 HotSpot의 바이어스 잠금을 참조하십시오.