[Effective Java] 스레드 안전을 문서화 하자. |
-
클래스 행동을 문서화하지 않으면, 프로그래머는 가정에 의존해서 그 클래스를 사용해야 한다.
만일 그런 가정들이 잘못되면, 그로 인한 프로그램은 불충분한 동기화나 과도한 동기화를 하게 될 것이다.
어떤 경우든, 심각한 에러가 유발될 수 있다.
-
메소드 선언부의 synchronized 변경자는 메소드의 상세 구현 부분이지 외부로 제공되는 API 가 아니다.
즉 Javadoc 에 synchronized 가 공개되지 않는다.
synchronized 변경자가 있다는 것이 스레드 안전을 문서화하기에 충분한 것은 아니다.
동시적 사용을 안전하게 하려면, 해당 클래스가 어떤 수준의 스레드 안전을 지원하는지 명확하게 문서화해야 한다.
-
다음은 스레드 안전 수준을 요약한 것이다.
불변(immutable)
이 클래스의 인스턴스는 상수를 나타내므로 별도의 외부 동기화가 필요 없다.
무조건적 스레드 안전(unconditionally thread-safe)
인스턴스는 가변적.
그러나 이 클래스의 인스턴스가 외부 동기화 없이 동시적으로 사용될 수 있을 만큼 내부 동기화가 충분하다.
Random, ConcurrentHashMap 등이 그 예
조건적 스레드 안전(conditionally thread-safe)
무조건적 스레드 안전과 같으나, 안전한 동시적 사용을 위해 일부 메소드에서 외부 동기화를 필요로 한다.
외부 동기화를 필요로 하는 Collections.synchronized wrapper 메소드들이 반환하는 컬렉션들이 여기에 해당한다.
스레드 안전하지 않음(not thread-safe)
동시적으로 사용하려면, 클라이언트에서 메소드 호출시 자신이 선택한 방법으로 외부 동기화해줘야 한다.
스레드 적대(thread hostile)
모든 메소드 호출 시 외부 동기화를 하더라도 이 클래스는 동시적 사용에 안전하지 않다.
동기화하지 않고 static 데이터를 변경하면 스레드 적대가 초래된다.
위의 안전 수준은 자바 동시성의 스레드 안전 annotations 의
불변(Immutable), 스레드 안전(ThreadSafe), 스레드 안전하지 않음(NotThreadSafe)와 대응된다.
-
조건적 스레드 안전에 해당하는 클래스를 문서화할 때는 주의가 필요하다.
메소드 호출을 어떤 순서로 할 때 외부 동기화가 필요하고, 그런 순서로 메소드를 실행할 때 어떤 락(드물지만)을 획득해야 하는지를 나타낸다.
-
enum 타입의 불변성은 문서화할 필요 없다.
-
전역적으로 접근 가능한 락을 사용하는 클래스에서는, 클라이언트가 순차적인 메소드 호출을 자동으로 실행할 수 있게 해 준다.
그러나 이런 유연성에는 비용이 따른다.
실수나 고의적으로 일어날 수 있는 일인데, 클라이언트가 전역적으로 접근 가능한 락을 오랜 기간 동안 잡고 있으면서 서비스 거부 공격(denial-of-service attack을 감행할 수 있다.
이런 경우 private 락 객체(private lock object)를 사용해야 한다.
private lock 객체는 final 로 만들어 부주의한 내용 변경을 막는 것이 좋다.
private lock 객체는 상속을 위해 설계된 클래스에 특히 잘 맞는다.
자신의 인스턴스를 락 객체로 사용하는 경우 서브 클래스에서 베이스 클래스의 오퍼레이션을 실수로 쉽게 방해할 수 있다. 그 반대도 마찬가지.
-
조건적 스레드 안전 클래스는 private 락 객체를 사용하기 어렵다.
특정 메소드 호출을 수행할 때 클라이언트가 어떤 락을 획득해야하는지 문서화해야 하기 때문이다.
Summary
모든 클래스는 자신의 스레드 안전 속성을 명확하게 문서화해야 한다.
내용을 작성할 떄는 신중하게 문장을 기술하거나 쓰레드 안전 annotation 을 사용한다.
synchronized 변경자는 문서에 나타내지 않는다.
조건적 스레드 안전에 해당하는 클래스에서는 메소드 호출을 어떤 순서로 할 때 외부 동기화가 필요하고, 그런 순서로 메소드를 실행할 때 어떤 락을 획득해야 하는지를 문서화해야 한다.
무조건적 스레드 안전에 해당하는 클래스를 작성한다면, 동기화된 메소드 대신 private 락 객체의 사용을 고려하자.
그렇게 하면, 클라이언트와 서브 클래스가 동기화를 방해하는 것을 막을 수 있으며 동시성을 제어하는 더욱 복잡한 방법을 향후 배포판에서 채택할 수 있는 유연성을 제공한다.
'프로그래밍 놀이터 > 디자인 패턴, 리펙토링' 카테고리의 다른 글
[Effective Java] 스레드 스케쥴러에 의존하지 말자 (0) | 2017.03.17 |
---|---|
[Effective Java] 늦 초기화를 분별력 있게 사용하자 (0) | 2017.03.16 |
[Effective Java] wait 와 notify 대신 동시성 유틸리티를 사용하자. (0) | 2017.03.13 |
[Effective Java] 스레드 그룹보다는 실행자와 작업을 사용하자. (0) | 2017.03.10 |
[Effective Java] 지나친 동기화는 피하자 (0) | 2017.03.09 |
댓글