컴퓨터 과학에서 사용하는 원자적 연산은 더이상 나눌 수 없는 단위로 수행된다는 것을 의미합니다
멀티스레드 환경에 이 내용을 적용하면 다른 스레드의 간섭 없이 안전하게 처리되는 연산이라는 뜻입니다
volatile int i = 0
라는 필드가 있을 때, i = 1
은 원자적 연산이지만
i = i+1
은 원자적 연산이 아닙니다
이 연산은 오른쪽 값을 읽고 읽은 값에 1을 더해서 11을 만들고
다시 i 변수에 대입하는 여러 순서로 이루어지기 때문입니다
원자적 연산은 멀티 스레드 상황에서 아무런 문제도 발생하지 않지만,
원자적 연산이 아닌 경우는 synchronized나 Lock 등을 통해서 안전한 임계영역을 만들어야 합니다
만약 위와 같은 연산이 동시에 이루어진다고 했을 때, 문제가 발생할 수 있습니다
이 문제는 게시판 조회수 기능을 만들 때 확인할 수 있습니다
너무 유명한 글이 되어서 해당 글을 동시에 보는 사람이 엄청 많다고 할 때,
여러 스레드가 동시에 원자적이지 않은 조회수 증가 로직을 호출한다면 1000명이 봤다고 해도
그것보다 적은 910명으로 보이거나 할 수 있습니다
volatile을 사용해도 해결되지 않습니다
volatile은 캐시 메모리를 무시하고 메인 메모리를 직접 사용하는 것일 뿐이라
여전히 이 문제는 메인 메모리를 사용할 때도 발생가능합니다
이 문제는 synchronized 블록이나 Lock을 통해 안전한 임계영역을 만들어주어야 합니다
이렇게 하면 정확하게 1000이라는 조회수가 나올 수 있습니다
자바는 이렇게 멀티스레드 상황에서 안전하게 조회수 증가 같이 증가 연산을 수행할 수 있는
AtomicInteger 클래스를 제공합니다
AtomicInteger viewCount = new AtomicInteger(0);
viewCount.incrementAndGet();
이렇게 안전하게 조회수를 증가시킬 수 있습니다!
CPU 캐시를 사용하기 때문에 가장 빠릅니다
대신 안전한 임계영역이 없고, volatile을 사용하지 않기 때문에 멀티스레드 상황에서 사용할 수 없습니다
다만 단일 스레드가 사용하는 경우에는 효율적입니다
CPU 캐시를 사용하지 않고 메인 메모리를 사용하며, 안전한 임계영역이 없어
멀티스레드 상황에서는 사용할 수 없습니다
또한 단일 스레드가 사용하기에는 앞선 단순 Integer보다 느리며, 멀티스레드 상황에서 안전하지 않습니다
뒤에 나올 Integer들 보다는 빠릅니다
뒤에 나올 Integer까지해서 제일 느립니다
하지만 안전한 임계영역이 있기 때문에 멀티스레드 상황에서도 안전하게 사용할 수 있습니다
자바가 제공하는 AtomicInteger를 사용하며, 멀티스레드 상황에서 안전하게 사용할 수 있습니다
synchronized와 Lock(ReentrantLock)을 사용하는 경우보다 약 1.5배 ~ 2배 정도 빠릅니다
AtomicInteger가 synchronized Integer보다 빠른 이유는 락을 사용하지 않고
원자적 연산을 만들어내기 때문입니다
그리고 그 방법이 바로 CAS 연산입니다