객체가 스레드에 안전해야 하는지는 해당 객체에 여러 스레드가 접근하는지에 따라 다르다.
스레드가 하나 이상의 상태 변수에 접근하고 그 중 하나라도 변수에 값을 쓰면, 스레드를 안전하게 만들기 위해 변수에 접근할 때 모든 스레드가 동기화를 통해 조율해야 한다.
여러 스레드가 변경할 수 있는 하나의 상태 변수를 동기화 없이 접근하면 그 프로그램은 잘못된 것이다. 이렇게 잘못된 프로그램을 고치는 데는 3가지 방법이 있다.
- 해당 상태 변수를 스레드 간에 공유하지 않거나
스레드 안전한 클래 클래스를 설계할 땐 바람직한 객체 지향 기법이 좋다. 캡슐화와 불변 객체를 잘 활용하고, 불변 조건을 명확하게 기술해야 한다.
여러 스레드가 클래스에 접근할 때 계속 정확하게 동작하면 해당 클래스는 스레드 안전하다.
여러 스레드가 클래스에 접근할 때, 실행 환경이 해당 스레드들의 실행을 어떻게 스케줄하든 어디에 끼워 넣든, 호출하는 쪽에서 추가적인 동기화나 다른 조율 없이도 정확하게 동작하면 클래스는 스레드 안전하다.
정확성은 클래스가 해당 클래스의 명세에 부합한다는 뜻으로, 잘 작성된 클래스 명세는 아래의 조건을 정의한다.
- 객체 상태를 제약하는 불변조건 (invariants)
→ 정확하게 동작한다는 건 클래스가 정확성 또는 코드 신뢰도를 갖춰 클래스 명세를 활용 가능하다는 뜻이다.
스레드 안전성이 필요한 경우는 직접 스레드를 생성하는 경우보다 서블릿 프레임워크 같은 수단을 사용하기 때문인 경우가 꽤 많다.
스레드 안전한 서블릿 기반 서비스 StatelessFactorizer
@ThreadSafe
public class StatelessFactorizer implements Servlet {
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
encodeIntoResponse(resp, factors);
}
}
StatelessFactorizer
는 상태가 없는 서블릿 객체로, 상태 없는 객체에 접근하는 스레드가 어떤 일을 하든 다른 스레드가 수행하는 동작의 정확성에 영향을 끼칠 수 없기 때문에 상태없는 객체는 항상 스레드 안전하다.