이전 포스팅으로 동시성 제어를 하는 방법에는 synchronized/ReentrantLock/ConcurrentHashMap가 있다고 했다.
이번 포스팅에서 세가지 방법을 java코드로 좀 더 자세히 구현해 보려고 한다.
동기화가 필요한 메서드나 코드블럭 앞에 사용하여 동기화 할 수 있다.
Synchronized로 지정된 임계영역은 한 스레드가 이 영역에 접근하여 사용할 때 lock이 걸림으로써 다른 스레드가 접근할 수 없게 된다.
A라는 스레드가 임계영역의 코드를 모두 실행 후 벗어나게 되면 unlock상태가 되어 대기하고있던 B스레드가 임계영역에 접근하여 다시 lock을 걸고 사용할 수 있게되는 형태.
lock은 해당 객체당 하나씩 존재하며, Synchronized로 설정된 임계영역은 lock 권한을 얻은 하나의 객체만이 독점적으로 사용하게 된다.
synchronized void usePoint() {
point--;
System.out.println(point);
}
public class ViewCounter {
private Integer count = 0; // 변수는 객체여야 한다.
public void view2() { // 변수 Lock
synchronized (this.count) { // 변수는 객체여야 한다.
this.count += 1;
System.out.println("currentThreadName : " + Thread.currentThread().getName() + ", view Count : " + this.count;
}
}
}
Synchronized를 적용하게 되면 하나의 스레드가 해당 메서드를 실행하고 있을 때 다른 메서드가 해당 메서드를 실행하지 못하고 대기하게 된다.
여러스레드가 동시에 접근할 수 없게 만들어 동시성 이슈를 막을 수 있다.
단점 : 한 번에 하나의 스레드만 메서드를 실행시킬 수 있어 병렬성이 매우 낮아짐.
=> 성능이슈 있을 수 있음.
메서드에 lock을 걸면 해당 메서드에 진입하는 스레드는 단 하나만 가능
변수에 lock을 걸 경우 해당 변수를 단 하나의 스레드만 참조할 수 있다.
하나의 객체에 여러개의 스레드가 접근해서 처리하고자 할 때.
static으로 선언한 객체에 여러 스레드가 동시에 사용될 때
변수를 공유할땐 static 변수사용, 하지만 static변수는 객체를 생성할 수 없다.
Atomic은 기존변수에 동시성 제어를 하는것이 아니라 AtomicType객체를 생성해서 해당 AtomicType객체에 대한 동시성을 제어하는 것이다.
새롭게 생성할 수 없는 static변수나 기존 변수에 동시성을 제어하기 위해선 synchoronized 키워드를 붙여서 사용한다.
Volatile은 여러 최적화 기법 중 캐싱과 리오더링으로 발생할 수 있는 이슈를 예방할 수 있다.

Multi thread환경에서 Thread가 변수 값을 읽어올 때 각각의 CPU Cache에 저장된 값이 다르기 때문에 변수 값 불일치 문제가 발생하게 된다.
변수를 volatile로 선언하게 되면
스레드가 변경한 값이 메인 메모리에 저장되지 않아서 다른 스레드가 이 값을 볼 수 없는 상황을 '가시성'문제라 한다.
한 스레드의 변경이 다른 스레드에게 보이지 않는다.

public class SharedObject {
public volatile int counter = 0;
}
counter변수에 volatile키워드를 선언하면 이 변수에 대한 쓰기 작업은 즉각 메인 메모리로 이루어질 것이고,
읽기 작업 또한 메모리로부터 다이렉트로 이루어진다.
공유 변수에 대한 업데이트는 항상 이를 읽고자 하는 모든 스레드들에 대해 visible하며 메모리 가시성 문제를 해결할 수 있다.
volatile의 몇 가지 중요한 포인트는 아래와 같다.
이를 volatile 변수의 가시성 보장(visibility guarantee)이라 한다.
Synchronized키워드는 Mutual Exclusion()와 Visibility 이 두 가지를 보장한다.
- Mutual Exclusion - 임계영역에서는 한 번에 하나의 스레드만 실행되도록 해야 한다.
- Visibility - 데이터 일관성을 유지하기 위해 한 스레드에서 공유 데이터에 대해 변경한 내용을 다른 스레드에서도 볼 수 있어야 한다.
반면 volatile변수는 가시성 특징은 갖고 있으나 원자성 특징을 갖고있지않다.
volatile변수의 값은 캐싱되지 않으며 모든 읽기와 쓰기 작업은 메인 메모리에서 직접 일어나게 된다.
하나의 Thread가 아닌 여러 Thread가 write하는 상황에서는 적합하지 않다.
여러 Thread가 write하는 상황이라면?
synchronized를 통해 변수 read & write의 원자성(atomic)을 보장해야 한다.
공유 중인 가변 데이터에 여러 개의 스레드가 동시에 접근하게 되면 Race Condition이 발생할 수 있고, 가시성 문제가 발생할 수 있다.
Atomic의 핵심은 스레드들이 blocking되고 다시 resuming되는 과정에서 시스템 자원을 소모하게 되는데 이런 소모비용을 줄이는 non-blocking방식을 사용한다.
어떤 스레드도 suspended되지 않기 때문에 context switch를 피할 수 있다.

Synchronized의 경우 Synchronized진입 전 메인 메모리와 CPU캐시 메모리 값을 동기화 하여 문제가 없도록 처리한다. 
[AtomicInteger클래스의 내부]
volatile int value;에서
volatile 키워드가 붙어있는 객체는 CPU캐시가 아닌 메모리에서 값을 참조한다.
volatile 키워드는 오직 한개의 쓰레드에서 쓰기작업을할때, 그리고 다른 쓰레드는 읽기작업만을 할떄 안정성을 보장한다.
하지만 AtomicInteger는 여러 쓰레드에서 읽기/쓰기작업을 병행한다.
그래서 CAS 알고리즘을 사용하여 2중 안전을 기하는 방법을 사용한다.
참조
혼동되는 synchronized 동기화-정리
Java 동시성 제어 - 멀티스레드, Syncronized, volatile, Atomic
[Java] volatile
java - Automic변수