CodeGym/Java Course/모듈 3/Java의 원자적 연산

Java의 원자적 연산

사용 가능

원자적 연산의 등장을 위한 전제 조건

원자성 작업이 작동하는 방식을 이해하는 데 도움이 되는 이 예를 살펴보겠습니다.

public class Counter {
    int count;

    public void increment() {
        count++;
    }
}

하나의 스레드가 있으면 모든 것이 잘 작동하지만 멀티스레딩을 추가하면 잘못된 결과를 얻게 되며 증가 작업이 하나의 작업이 아니라 3개이기 때문입니다. 현재 값을 가져오기 위한 요청세다, 그런 다음 1씩 증가시키고 다시 씁니다.세다.

그리고 두 스레드가 변수를 증가시키려고 하면 데이터가 손실될 가능성이 큽니다. 즉, 두 스레드 모두 100을 수신하므로 결과적으로 둘 다 예상 값 102 대신 101을 씁니다.

그리고 그것을 해결하는 방법? 자물쇠를 사용해야 합니다. synchronized 키워드는 이 문제를 해결하는 데 도움이 되며, 이를 사용하면 한 번에 하나의 스레드가 메서드에 액세스할 것이라는 보장이 제공됩니다.

public class SynchronizedCounterWithLock {
    private volatile int count;

    public synchronized void increment() {
        count++;
    }
}

또한 스레드 간 참조의 올바른 가시성을 보장하는 volatile 키워드를 추가해야 합니다. 위에서 그의 작업을 검토했습니다.

그러나 여전히 단점이 있습니다. 가장 큰 것은 성능입니다. 많은 스레드가 잠금을 획득하려고 시도하고 하나가 쓰기 기회를 얻는 시점에서 나머지 스레드는 스레드가 해제될 때까지 차단되거나 일시 중단됩니다.

이러한 모든 프로세스, 차단, 다른 상태로의 전환은 시스템 성능에 매우 비쌉니다.

원자성 작업

이 알고리즘은 비교 및 ​​교환(CAS, 비교 및 ​​교환, 데이터 무결성을 보장하고 이에 대한 많은 연구가 이미 있음)과 같은 저수준 기계 명령어를 사용합니다.

일반적인 CAS 작업은 세 개의 피연산자에서 작동합니다.

  • 작업을 위한 메모리 공간(M)
  • 변수의 기존 기대값(A)
  • 설정할 새 값(B)

CAS는 M을 B로 원자적으로 업데이트하지만 M의 값이 A와 동일한 경우에만 그렇지 않으면 아무런 조치도 취하지 않습니다.

첫 번째와 두 번째 경우에는 M 값이 반환되므로 값 가져오기, 값 비교 및 ​​업데이트의 세 단계를 결합할 수 있습니다. 그리고 이 모든 것이 기계 수준에서 하나의 작업으로 바뀝니다.

다중 스레드 응용 프로그램이 변수에 액세스하고 업데이트를 시도하고 CAS가 적용되는 순간 스레드 중 하나가 변수를 가져오고 업데이트할 수 있습니다. 그러나 잠금과 달리 다른 스레드는 값을 업데이트할 수 없다는 오류를 받게 됩니다. 그런 다음 추가 작업으로 이동하며 이러한 유형의 작업에서는 전환이 완전히 제외됩니다.

이 경우 CAS 작업이 성공적으로 수행되지 않은 상황을 처리해야 하므로 논리가 더 어려워집니다. 작업이 성공할 때까지 코드가 이동하지 않도록 코드를 모델링할 뿐입니다.

원자 유형 소개

int 유형의 가장 간단한 변수에 대해 동기화를 설정해야 하는 상황에 처한 적이 있습니까 ?

우리가 이미 다룬 첫 번째 방법은 volatile + synchronized 를 사용하는 것입니다 . 그러나 특별한 Atomic* 클래스도 있습니다.

CAS를 사용하면 첫 번째 방법에 비해 작업이 더 빠르게 수행됩니다. 또한 값을 추가하고 작업을 증가 및 감소시키는 특별하고 매우 편리한 방법이 있습니다.

AtomicBoolean , AtomicInteger , AtomicLong , AtomicIntegerArray , AtomicLongArray 는 작업이 원자적인 클래스입니다. 아래에서 우리는 그들과의 작업을 분석할 것입니다.

AtomicInteger

AtomicInteger 클래스는 확장된 원자 연산을 제공하는 것 외에도 원자적으로 읽고 쓸 수 있는 int 값 에 대한 연산을 제공합니다 .

변수를 읽고 쓰는 것처럼 작동하는 getset 메서드가 있습니다 .

즉, 이전에 이야기한 동일한 변수의 후속 수신과 함께 "이전에 발생"합니다. 원자성 compareAndSet 메서드에는 이러한 메모리 일관성 기능도 있습니다.

새 값을 반환하는 모든 작업은 원자적으로 수행됩니다.

int addAndGet (int 델타) 현재 값에 특정 값을 추가합니다.
부울 compareAndSet(예상 int, 업데이트 int) 현재 값이 예상 값과 일치하면 주어진 업데이트 값으로 값을 설정합니다.
int 감소 및 Get() 현재 값을 1씩 감소시킵니다.
int getAndAdd(int 델타) 주어진 값을 현재 값에 더합니다.
int getAndDecrement() 현재 값을 1씩 감소시킵니다.
정수 getAndIncrement() 현재 값을 1씩 증가시킵니다.
int getAndSet(int newValue) 주어진 값을 설정하고 이전 값을 반환합니다.
int 증가 및 Get() 현재 값을 1씩 증가시킵니다.
게으른 세트(int newValue) 마지막으로 주어진 값으로 설정합니다.
부울 weakCompareAndSet(예상, 업데이트 정수) 현재 값이 예상 값과 일치하면 주어진 업데이트 값으로 값을 설정합니다.

예:

ExecutorService executor = Executors.newFixedThreadPool(5);
IntStream.range(0, 50).forEach(i -> executor.submit(atomicInteger::incrementAndGet));
executor.shutdown();
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.HOURS);

System.out.println(atomicInteger.get()); // prints 50
1
과제
모듈 3,  레벨 19레슨 1
잠금
Earn a Million!
task4201
1
과제
모듈 3,  레벨 19레슨 1
잠금
Early Bird Gets the Worm
task4202
코멘트
  • 인기
  • 신규
  • 이전
코멘트를 남기려면 로그인 해야 합니다
이 페이지에는 아직 코멘트가 없습니다