Java Adv 14 - atomic operation - 3

AtomicInteger

자바는 앞서 만든 SyncInteger 와 같이 멀티스레드 상황에서 안전하게 증가 연산을 수행할 수 있는 AtomicInteger 라는 클래스를 제공한다. 이름 그대로 원자적인 Integer 라는 뜻이다. 다음과 같이 MyAtomicInteger 클래스를 만들고, 자바가 제공하는 AtomicInteger 를 사용해보자.

image

  • new AtomicInteger(0) : 초기값을 지정한다. 생략하면 0 부터 시작한다.
  • incrementAndGet() : 값을 하나 증가하고 증가된 결과를 반환한다.
  • get() : 현재 값을 반환한다.

image

실행 결과를 보면 AtomicInteger 를 사용하면 MyAtomicInteger 의 결과도 1000인 것을 확인할 수 있다. 1000개의 스레드가 안전하게 증가 연산을 수행한 것이다. AtomicInteger 는 멀티스레드 상황에 안전하고 또 다양한 값 증가, 감소 연산을 제공한다. 특정 값을 증가하거나 감소해야 하는데 여러 스레드가 해당 값을 공유해야 한다면, AtomicInteger 를 사용하면 된다.

성능 테스트

image image image

  • BasicInteger
    • 가장 빠르다.
    • CPU 캐시를 적극 사용한다. CPU 캐시의 위력을 알 수 있다.
    • 안전한 임계 영역도 없고, volatile도 사용하지 않기 때문에 멀티스레드 상황에는 사용할 수 없다.
    • 단일 스레드가 사용하는 경우에 효율적이다.
  • VolatileInteger
    • volatile 을 사용해서 CPU 캐시를 사용하지 않고 메인 메모리를 사용한다.
    • 안전한 임계 영역이 없기 때문에 멀티스레드 상황에는 사용할 수 없다.
    • 단일 스레드가 사용하기에는 BasicInteger 보다 느리다. 그리고 멀티스레드 상황에도 안전하지 않다.
  • SyncInteger
    • synchronized 를 사용한 안전한 임계 영역이 있기 때문에 멀티스레드 상황에도 안전하게 사용할 수 있다.
    • MyAtomicInteger 보다 성능이 느리다.
  • MyAtomicInteger
    • 자바가 제공하는 AtomicInteger 를 사용한다. 멀티스레드 상황에 안전하게 사용할 수 있다.
    • 성능도 synchronized , Lock(ReentrantLock) 을 사용하는 경우보다 1.5 ~ 2배 정도 빠르다.
    • SyncInteger 처럼 락을 사용하는 경우보다, AtomicInteger 가 더 빠른 이유는 무엇일까? i++ 연산은 원자적인 연산이 아니다.
    • 따라서 분명히 synchronized , Lock(ReentrantLock) 와 같은 락을 통해 안전한 임계 영역을 만들어야 할 것 같다.
    • 놀랍게도 AtomicInteger 가 제공하는 incrementAndGet() 메서드는 락을 사용하지 않고, 원자적 연산을 만들어낸다.

Reference

김영한님의 자바 강의

https://naveen-metta.medium.com/atomic-operations-in-java-mastering-thread-safety-and-concurrency-7c3360ec0bc5



© 2022. by taewoo

Powered by taewoo