쓰레드는 공유된 자원을 통해 의사소통
효과적이지만 2가지 에러 발생 가능 : thread interference(간섭) error와 memory consistency(일관성) error
이 두가지 에러를 예방하기 위해 synchronization을 사용
동기화를 하면 starvation 이나 livelock 같은 thread contention과 race condition을 예방할 수 있음
(race condition : 쓰레드가 어떻게 스케쥴링 되느냐에 따라 결과가 바뀌는 상황 )
counter = 0;
add(){ counter++}
get(){return counter }
이경우 여러 쓰레드들이 동시에 counter변수에 접근하면 원하는 결과 x인 경우 존재
이러한 경우는 쓰레드 간섭이라고 함
ex) T1이 add T2 add 해서 결과가 counter 2가 아닌 1이 나옴
여러 쓰레드가 같은 데이터에 대해 다른 뷰를 가진 상황
발생 원인은 매우 복잡
피하는 방법이 중요
happens-before 관계를 이해하는 것이 피하는 방법의 핵심
happens-before 관계란 하나의 특정한 statement에 의해 메모리 write한 것이 다른 특정 statement에게 visible함을 보장한다.
happens-before 관계를 만드는 3가지 방법
1. 동기화
2. Thread.start
새로운 쓰레드
3. Thread.join
메소드 앞에 synchronized 키워드 붙인 것
효과 2가지
1. 같은 객체의 non-static Synchronized Methods에 하나의 쓰레드만 접근 가능
다른 쓰레드가 접근하려면 먼저 접근한 쓰레드에서 메소드 종료해야함
( a 객체의 func1이 synchronized이면 하나의 쓰레드만 접근 가능 )
2. Synchronized Methods 종료 시 같은 Synchronized Methods 호출에 대해 happens-before 관계를 형성함
즉 종료시 동기화된 메소드를 가진 객체의 상태 변화에 대해 다른 모든 쓰레드들이 visible하게 된다는 것
동기화 메소드는 쉽게 쓰레드 간섭 에러와 메모리 일관성 에러를 예방하게 해준다
효과적이지만 liveness 문제를 발생시킬 수 있다.
동기화는 intrinsic lock 또는 monitor lock이라 부르는 내부의 엔티티에 의해 만들어졌다.
(API 스펙에서 이 엔티티를 간단하게 monitor라고 부르기도 한다)
intrinsic lock은 동기화의 2가지 관점에서 역할을 한다.
1. 객체의 상태에 상호배제적으로 접근하게하기
2. happens-before 관계 형성
Locks In Synchronized Methods
쓰레드가 동기화 메소드를 호출하면 호출된 메소드의 객체에 대한 내부 락을 얻고 메소드 반환시 락도 푼다.
예외에 발생해 메소드에서 반환되도 락 품
static 동기화 메소드가 호출되면 Class 객체에 대한 내부 락을 얻는 것
public class A{
private int c = 0;
private static int d = 0;
public synchronized void increment1() {
c++;
}
public void increment2() {
synchronized(this)
c++;
}
public static synchronized void increment3(){
d++;
}
public static void increment3() {
synchronized(A.class)
d++;
}
}
increment1 와 increment2 같은 의미
increment3 와 increment4 같은 의미
락을 걸 객체를 지정해서 사용
객체 전체에 락을 걸지 않으므로 조심해야함
다른 쓰레드가 가지고 있는 락을 획득할 수 없지만 자신이 이미 가진 락을 다시 얻을 수 있다.
같은 락을 한번이상 같는 것을 Reentrant Synchronization라고 함
한번에 일어나는 것을 원자적이라고 함
중간에 멈출 수 없음, 일어나거나 일어나지 않거나 둘중 하나
레퍼런스 변수나 대부분의 원시 변수( long 과 double 제외) r/w 모두 원자적
volatile이 선언된 모든 변수는 r/w 모두 원자적
원자적 행동은 중간에 간섭받을 수 없으므로 쓰레드 간섭에서 제외됨
그러나 모든 동기화 원자 행동이 필요없는건 아님
왜냐하면 메모리 일관성 문제는 여전히 가능
volatile 변수가 메모리 일관성 에러를 줄여줄 수 있음
왜냐하면 같은 변수에 대한 후속 읽기에 대해 happen-before 관계를 형성함
즉 volatile 변수를 바꾸면 언제나 다른 쓰레드들도 바뀐걸 알 수 있음
또한 쓰레드가 volatile 변수를 읽으면 volatile 변수의 최신상태를 알 수 있을뿐만 아니라 그 변화를 일으킨 사이드 이펙트또한 알수있음
간단한 원자성 변수에 접근하는것이 동기화 코드를 통해 변수에 접근하는거보다 효과적이지만 메모리 일관성 문제 조심해야함
질문 1 ) Double Long 변수는 r/w atomic?
질문 2 ) static 변수 접근 메소드가 하나는 syn static이고 하나는 syn non-static이면 두 메소드의 락이 다름 -> thread safe 하지 않는 상태?
출처 : https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html