아이템 70. 스레드 안정성에 대해 문서로 남겨라
70. 스레드 안정성에 대해 문서로 남겨라
스레드 안정성에 대한 수준별 요약
- 변경 불가능(immutable) : 이 클래스로 만든 객체들은 상수다. 따라서 외부적인 동기화 메커니즘 없이도 병렬적 이용이 가능하다.
String
,Long
,BigInteger
등이 있다. - 무조건적 스레드 안전성(unconditionally thread-safe) : 이 클래스의 객체들은 변경이 가능하지만
적절한 내부 동기화 메커니즘을 갖추고 있어 외부적으로 동기화 메커니즘을 적용하지 않아도 병렬적으로 사용 할수 있다.
Random
,ConcurrentHashMap
등이 있다. - 조건부 스레드 안정성(conditionally thread-safe) : 외부적 동기화 없이는 병렬적으로 사용할수 없는 겍체,
예를 들어,
Collections.synchronized
계열 메서드가 반환하는 포장 객체를 사용할때, 이런 객체들의 반복자(iterator)는 외부적 동기화 없이는 병렬적으로 사용할수 없다. - 스레드 안정성 없음 : 해당 객체들을 병렬적으로 사용하려면, 메서드를 호출하는 부분을 클라이언트 코드에서
선택한 외부적 동기화 수단으로 감싸야 한다.
ArrayList
나HashMap
같은 일반 용도의 컬렉션 구현체들 이에 해당한다. - 다중 스레드에 적대적(thread-hostile) : 이런 클래스의 객체는 설사 메서드를호출하는 모든 부분을 외부적 동기화 수단으로 감싸더라도 안전하지 않다. 이런 클래스가 되는 것은 보통, 동기화 없이 정적 데이터(static data)를 변경하기 때문이다.
조건부 스레드 안정성 반복처리를 위한 숙어
Collections.synchronizedMap
의 문서를 보면 아래와 같이 명세되어 있다.
Map<K, V> m = Collections.synchronizedMap(new HashMap<K, V>());
...
Set<K> s = m.ketSet(); // 동기화 블록 안에 있을 필요가 없음
...
synchronized(m) { // s가 아니라 m에 동기화
for (K key : s)
key.f();
}
이 지침을 따르지 않을 경우 프로그램은 비결정적(non-deterministic)으로 동작할 수 있다.
private 락 객체 패턴 숙어
// DoS 공격을 피하기 위한 private lock 객체 숙어
private final Object lock = new Object(); // 이렇게 하면 실수로, lock필드의 내용을 변경하는 것을 막을 수 있음
public void foo() {
synchronized(lock) {
...
}
}
private락 객체 패턴은 무조건적 스레드 안전성을 제공하는 클래스에만 적용할 수 있다.
결론
- 어떤 방식(말로 풀건, 스레드 안정성 어노테이션등)을 이용해서 모든 클래스는 자신의 스레드 안정성을 분명히 밝혀야한다.
- synchronized 키워드는 메서드의 구현 상세(implementation detail)에 해당하는 정보이며, 공개 API의 일부가 아니기 때문에 문서적으로 전혀 의미없다.
- 무조건적 스레드 안정성을 가진다면 private lock 객체 숙어 를 사용을 고려하라.