이펙티브자바 규칙66: 변경 가능 공유 데이터에 대한 접근은 동기화하라
아래 코드는 실행한지 1초가 지나면 stopRequested
변수를 true
로 바꿔주고 프로그램이 종료되도록 기대하면서 작성된 코드입니다.
하지만 실제로 아래 코드를 돌려보면 절대 종료 되지 않습니다.
1 | import java.util.concurrent.TimeUnit; |
그 이유는 위 코드는 동기화 메커니즘을 적용하지 않고 멀티 쓰레드로 개발을 했기 때문에 **main 쓰레드**에서 변경한 stopRequest
의 새로운 값을 backgroundThread 에서 언제 확인하게 될지 알 수가 없기 때문입니다.
책에서는 이 문제를 해결하기 위한 솔루션으로 두가지를 제시 합니다.
synchronized
키워드를 사용하여stopRequested
변수를 동기화 하는 방법.volatile
키워드를 사용하여 lock없이 모든 쓰레드가 최근에 기록된 값을 읽어가도록 보장하는 방법
synchronized
키워드를 이용한 방법의 코드
1 | import java.util.concurrent.TimeUnit; |
쓰기 메서드 (requestStop
)와 읽기 메서드(stopRequested
) 모두에 동기화 메커니즘이 적용되어 있습니다.
** 읽기와 쓰기 모두에 적용하지 않으면 동기화는 아무런 효과도 없다** 고 책에서 강조하고 있습니다.
위 코드에서 동기화 메서드가 하는 일은 실제 그 메서드 들의 상호 배제성을 위해서라기보다는 (Mutual Exclusion) stopRequested
변수의 가시성(Visibility
)을 보장하기 위해서였습니다.
이 부분은 이 블로그를 한번 읽어보시면 도움이 됩니다.
사실 단순히 그 목적이라면 성능 향상을 위해 해당 객체를 lock 잡지 않고 volatile로 선언하기만 해도 해결할 수 있습니다.
volatile
키워드를 이용한 코드
1 | import java.util.concurrent.TimeUnit; |
맨 처음의 잘못된 코드와 다른점은 stopRequested
변수를 volatile
로 선언한 것 밖에 없지만 1초후에 정상적으로 종료 되는것을 확인 할 수 있습니다.
그럼 long과 double을 제외하고서는 기본적으로 원자성을 보장 해 주고….
위와같이 volatile
키워드를 사용하면 동기화 블럭이나 동기화 함수를 만들지 않고도 Thread safe 하게 변수를 공유하는 프로그램을 짤 수 있겠다!!!
하지만 아래와 같은 코드는 ++연산자가 원자적이지 않기 때문에 쓰레드 쎄이프 하지 않습니다.
1 | private static volatile int nextSerialNumber = 0; |
위 코드는 synchronized block
을 사용해서 해결 할 수 도 있지만 java.util.concurrent.atomic
에 속해있는 AtomicLong
을 사용하여 해결하는것이 성능적으로 더 좋습니다.
1 | private static final AtomicLong nextSerialNumber = new AtomicLong(); |
# 결론
간단하게 쓰레드 간에 변수만 공유하는 경우에 대해서 알아봤는데요 아래 나열한 내용을 숙지하고 프로그램 한다면 좋을것 같습니다.
- 변경가능 데이터는 한 스레드에서만 이용하는 것이 가장 좋다.
- 하지만 그게 불가능 하다면 변경 가능한 데이터를 읽거나 쓰는 모든 쓰레드는 동기화를 수행해야 함.
- 원자성이 보장되는 경우는
volatile
키워드만으로 안전하게 데이터를 교환할 수 있음. java.util.concurrent.atomic
에 속해있는 클래스를 사용하는것은 좋은 해법임.