응답 불가와 안전 실패를 피하려면 동기화 메서드나 동기화 블록 안에서는 제어를 절대로 클라이언트에게 양도하면 안된다.
- 여러 쓰레드가 한 개의 리소스를 사용하려고 할 때, 사용하려는 쓰레드를 제외한 나머지들이 접근하지 못하게 막는 것 →
Thread Safe
- 동기화 방법
- 메서드 자체
synchronized로 선언하는 방법
- 블록으로 객체를 받아 lock을 거는 방법 -
synchronized(this)
- 멀티 쓰레드 프로세스는 동시성 또는 병렬성으로 실행된다.
- 동시성 : 하나의 코어에서 여러 개의 프로세스가 번갈아 가면서 실행된다.
- 병렬성 : 멀티 코어에서 개별 쓰레드를 동시에 실행한다.
- 동기화(synchronized)된 코드 블럭 안에서는 재정의 가능한 메서드를 호출해선 안된다.
- 클라이언트가 넘겨준 함수 객체를 호출해서도 안 된다.
- 이런 메서드는 동기화된 클래스 관점에서 외계인 메서드라고 한다.(무슨 일을 할지 모르니, 이 메서드가 예외를 발생시키거나 교착상태를 만들거나 데이터를 훼손시킬 수 있다.)
public class ObservableSet<E> extends ForwardingSet<E>{
public ObservableSet(Set<E> set) {
super(set);
}
private final List<SetObserver<E>> observers = new ArrayList<>();
public void addObserver(SetObserver<E> observer){
synchronized (observers){
observers.add(observer);
}
}
// 외계인 메서드
private void notifyElementAdded(E element){
synchronized (observers){
for(SetObserver observer: observers){
observer.added(this,element);
}
}
}
public boolean removeObserver(SetObserver<E> observer){
synchronized (observers){
return observers.remove(observer);
}
}
@Override
public boolean add(E element) {
boolean added = super.add(element);
if(added)notifyElementAdded(element);
return added;
}
public static void main(String[] args) {
ObservableSet<Integer> set = new ObservableSet<>(new HashSet<>());
// 23 일 때 , 지우는 조건 추가
// 재정의가 가능한 메서드 호출을 금지
set.addObserver(new SetObserver<Integer>() {
@Override
public void added(ObservableSet<Integer> set, Integer element) {
System.out.println(element);
if (element == 23)
set.removeObserver(this);
}
});
for (int i = 0; i < 100; i++) {
set.add(i);
}
}
}
- 자바의 고유 락은 재진입이 가능하다. 재진입 가능하다는 것은 락 획득이 호출 단위가 아니라 쓰레드 단위로 일어난다는 것을 의미한다.
- 이미 락을 획득한 쓰레드는 같은 락을 얻기 위해 대기할 필요가 없다.
public class Reentrancy {
public synchronized void a() {
System.out.println("a");
// b가 synchronized로 선언되어 있지만 a진입시 이미 락을 획득하였으므로,
// b를 호출할 수 있다.
b();
}
public synchronized void b() {
System.out.println("b");
}
public static void main(String[] args) {
new Reentrancy().a();
}
}
- 자바의 동기화 비용은 빠르게 낮아져왔으나, 과도한 동기화를 피하는 일 역시도 중요하다.
- 멀티코어가 일반화된 오늘날 과도한 동기화가 초래하는 진짜 비용은 락을 획득하는데 드는 CPU 시간이 아니다.
- 쓰레드 간 경쟁하는 Race Condition에 낭비가 발생하는 것이다.
- 병렬로 실행할 기회를 잃게 된다.
- 모든 코어가 메모리를 일관되게 바라보기 위한 지연 시간이 진짜 비용
- 가상 머신의 코드 최적화를 제한하는 점도 숨은 비용
- 기본 규칙은 동기화 영역에서 가능한 한 일을 적게 하는 것을 우선으로
- 오래 걸리는 작업의 경우라면 동기화 영역 밖으로 옮기자.
- 여러 쓰레드가 호출할 가능성이 있는 메서드가 정적 필드를 수정한다면 그 필드를 사용하기 전에 반드시 동기화해야 한다.
- 교착 상태와 데이터 훼손을 피하려면 동기화 영역 안에서 외계인 메서드를 절대 호출하지 말자.
- 동기화 영역 안에서의 작업은 최소화하라.