멀티 스레드 환경에서 원자적 연산이 아닌 연산을 안전하게 할 수 있도록 해주는 클래스다.
Integer 외에도 AtomicLong, AtomicBoolean 등 다양한 Atomic 클래스가 있다.
주로 간단한 cpu 연산은 락보다는 이 CAS 연산인 AtomicInteger을 사용하는 게 효과적이다.
다만, 충돌이 많이 일어나는 환경에서는 효율이 떨어진다.
한 마디로 더이상 쪼개질 수 없는 연산이다. ex) i = 1
참고로 i++은 원자적 연산이 아니다. i = i + 1을 줄인 것으로,
1. 오른쪽 i의 값을 읽는다.
2. i에 1을 더한다.
3. 더한 값을 왼쪽 i에 대입한다.
이러한 3가지 단계로 이루어져 있어 원자적 연산이 아니다.
원자적 연산이 아닐 경우 멀티 스레드 환경에서 문제가 생길 수 있다. 이러한 문제를 해결해주는 것이 AtomicInteger이다.
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class AtomicIntegerExample {
// 일반 int 변수 (스레드 안전하지 않음)
private static int counter = 0;
// AtomicInteger 변수 (스레드 안전함)
private static AtomicInteger atomicCounter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
final int NUM_THREADS = 5; // 스레드 개수
final int INCREMENT_PER_THREAD = 10000; // 각 스레드가 증가시킬 횟수
// --- 일반 int 변수 테스트 ---
System.out.println("--- 일반 int 변수 테스트 시작 ---");
counter = 0; // 카운터 초기화
ExecutorService executor1 = Executors.newFixedThreadPool(NUM_THREADS);
for (int i = 0; i < NUM_THREADS; i++) {
executor1.submit(() -> {
for (int j = 0; j < INCREMENT_PER_THREAD; j++) {
counter++; // 스레드 안전하지 않은 증가
}
});
}
executor1.shutdown();
executor1.awaitTermination(1, TimeUnit.MINUTES); // 모든 스레드가 종료될 때까지 대기
System.out.println("기대하는 값: " + (NUM_THREADS * INCREMENT_PER_THREAD));
System.out.println("일반 int 변수 최종 값: " + counter); // 기대하는 값보다 작을 가능성이 높음
System.out.println("--- 일반 int 변수 테스트 종료 ---\n");
// --- AtomicInteger 변수 테스트 ---
System.out.println("--- AtomicInteger 변수 테스트 시작 ---");
atomicCounter.set(0); // Atomic 카운터 초기화
ExecutorService executor2 = Executors.newFixedThreadPool(NUM_THREADS);
for (int i = 0; i < NUM_THREADS; i++) {
executor2.submit(() -> {
for (int j = 0; j < INCREMENT_PER_THREAD; j++) {
atomicCounter.incrementAndGet(); // 원자적인 증가
}
});
}
executor2.shutdown();
executor2.awaitTermination(1, TimeUnit.MINUTES); // 모든 스레드가 종료될 때까지 대기
System.out.println("기대하는 값: " + (NUM_THREADS * INCREMENT_PER_THREAD));
System.out.println("AtomicInteger 변수 최종 값: " + atomicCounter.get()); // 항상 기대하는 값과 같음
System.out.println("--- AtomicInteger 변수 테스트 종료 ---");
}
}
AtomicInteger 변수를 만들어서 사용하면 되고, 괄호 안에 기본값을 넣을 수 있다. 안 넣으면 기본값은 0이다.
incrementAndGet()으로 값을 증가시키거나 decrementAndGet()으로 값을 감소시키는 등 다양한 연산을 할 수 있다.
AtomicInteger을 사용함으로써 synchronized나 lock과 비슷한 효과를 낼 수 있고 더 좋은 성능으로 안전하게 연산을 수행할 수 있다.
락을 사용하지 않기 때문이다.