한 메서드를 여러 스레드가 동시에 호출할 때 그 메서드가 어떻게 동작하느냐는 해당 클래스와 이를 사용하는 클라이언트 사이의 중요한 계약과 같다.
synchronized 한정자가 보이는 메서드는 스레드 안전하다는 이야기
-> 몇 가지 면에서 틀림
1. 자바독이 기본 옵션에서 생성한 API 문서에는 synchronized 한정자가 포함되지 않는다.
2. 메서드 선언에 synchronized 한정자를 선언할지는 구현 이슈일 뿐 API에 속하지 않는다.
위와 같은 이유로 이것만으로는 그 메서드가 스레드 안전하다고 믿기 어렵다.
스레드 안전성 높은 순서
1. 불변
- 이 클래스의 인스턴스는 마치 상수같아서 외부 동기화도 필요 없다
- 무조건적인 스레드 안전
- 이 클래스의 인스턴스는 수정될 수 있으나 내부에서 잘 동기화하여 외부 동기화 없이 동시에 사용해도 안전하다.
- 조건부 스레드 안전
- 무조건적 스레드 안전과 같으나 일부 메서드는 동시 사용을 위해 외부 동기화가 필요하다.
- 스레드 안전하지 않음
- 이 클래스의 인스턴스는 수정될 수 있다. 동시에 사용하려면 각각의 메서드 호출을 외부 동기화로 감싸야 한다.
- 스레드 적대적
- 모든 메서드 호출을 외부 동기화로 감싸도 멀티스레드 환경에서 안전하지 않다. 이 수준의 클래스는 보통 정적 데이터를 아무 동기화 없이 수정한다.
조건부 스레드 안전한 클래스는 주의해서 문서화해야 한다.
어떤 순서로 호출할 때 외부 동기화가 필요한지, 그리고 그 순서로 호출하려면 어떤 락 혹은 (드물게) 락들을 얻어야 하는지 알려줘야 한다.
일반적으로 인스턴스 자체를 락으로 얻지만 예외도 있다.
예를들어 Collections.synchronizedMap의 API 문서에는 다음과 같이 써 있다.
/** synchronizedMap이 반환한 맵의 컬렉션 뷰를 순회하려면 반드시 그 맵을 락으로 사용해
수동으로 동기화하라
**/
Map<K, V> m = Collections.synchronizedMap(new HashMap<>());
Set<K> s = m.keySet(); // 동기화 블록 밖에 있어도 된다.
...
synchronized(m) { // s가 아닌 m을 사용해 동기화해야 한다!
for (K key : s)
key.f();
}
// 이대로 따르지 않으면 동작을 예측할 수 없다.
클래스의 스레드 안전성은 보통 클래스의 문서화 주식에 기재하지만, 독특한 특성의 메서드라면 해당 메서드의 주석에 기재하도록 하자.
클래스가 외부에서 사용할 수 있는 락을 제공하면 클라이언트에서 일련의 메서드 호출을 원자적으로 수행할 수 있다.
// 비공개 락 객체 관용구 - 서비스 거부 공격을 막아준다.
private final Object lock = new Object();
public void foo() {
synchronized(lock) {
...
}
}