객체 구성

스레드 안전성 확보한 컴포넌트를 안전하게 서로 연결해 사용한다면 규모 있는 컴포넌트나 프로그램을 좀 쉽게 작성할 수 있을 것

스레드 안전한 클래스 설계

스레드 안전성 확보할 때 고려사항

객체의 상태를 보관하는 변수는?
그 변수가 가질 수 있는 값의 범위는?
객체 내부 값을 동시에 사용할 때 그 과정을 관리할 정책은?

객체 상태는 내부 변수에 의해서 결정된다. 동기화 정책 : 값이 계속 변하는 상황에서도 값을 안전하게 사용하도록 조절하는 방법

객체 불변성, 스레드 한정, 락 등 어떻게 사용해야 스레드 안전성을 확보할 수 있는지 명세
어떤 변수를 어떤 락으로 막아야 하는지 명세 (e.g. @GuardedBy)

동기화 요구사항 정리

스레드 안전성을 확보했다 
  =  여러 스레드가 동시에 클래스를 사용하는 상황에서, 클래스 내부의 값을 안정적인 상태로 유지할 수 있다.

상태 범위(state space)  
  = 객체와 변수가 가질 수 있는 가능한 값의 범위
  = 변경 불가한 val을 활용하면 상태 범위를 크게 줄여서 이해하기 쉽게 만든다.

다중 연산일 수밖에 없는 거, 즉, 단일 연산으로 만들기 위해 동기화 필요한 경우

현재 값을 기반으로 다음 상태를 바꾸는 경우
여러 변수를 통해 클래스 상태가 올바른지 아닌지 정의하는 경우
특정 연산을 실행했을 때 올바르지 않은 상태 값을 가질 수 있다면, 반드시 단일 연산으로 구현해야한다.

상태 의존 연산 (state-dependent)

상태를 기반으로 선행 조건을 가지는 연산
현재 조건에 따라 동작 여부가 결정되는 연산

이런 문제를 해결하기 위해서

  • 상태가 올바르게 바뀔 경우 기다리다가 실제 연산을 수행할 수 있다. (wait, notify)

  • 이미 구현된 라이브러리 사용하는 편이 간단하고 안전하다 (세마포어, 블로킹 큐)

상태 소유권

객체의 상태를 정의하고자 할 때, 객체가 실제로 소유하는 데이터만 기준으로 잡아야한다.

소유권?

가비지 컬렉션 기능을 고려한다면 객체 소유권 개념을 생각하기가 어렵다. C++은 특정 메소드에 객체 인스턴스를 넘겨줄 때 (내가 아직 정확하게 알지는 못하는 개념이다.)

객체와 함께 객체 소유권도 함께 넘기는지? (메모리 해체 책임이 거기?)
잠시만 사용하도록 빌려주는 형식인지? (메모리 해체 책임은 여기?)
메소드 인자로 넘기지만 계속해서 함께 사용할지? (새로운 객체 전달? 새로운 메모리 할당?)

특정 변수에 대한 소유권을 갖고 있으면, 그 변수의 상태가 올바르게 유지되도록 조절하는 락 구조에 대한 소유권도 가진다.

소유권 분리 형태를 사용하는 경우도 있다. 컬렉션 내부 구조에 대한 소유권은 컬렉션 클래스에, 컬렉션에 추가되어 있는 개체의 소유권은 클라이언트 쪽에 컬렉션에 들어있는 객체를 사용할 때 동기화 작업을 수행해야한다.

인스턴스 한정 (aka. 한정 기법, feat. 자바 모니터 패턴)

인스턴스 한정 = 객체를 적절하게 캡슐화 해서 스레드 안전성을 확보하는 방법
한정 기법과 락을 적절하게 활용하면 스레드 안전성이 검증되지 않은 객체도 마음 놓고 안전하게 사용 가능
데이터가 캡슐화되어 있다면, 해당 데이터에 지정된 락이 적용되는지 쉽고 정확하게 파악 가능하다.

다양한 레벨의 한정

클래스 인스턴스에 한정 (private 으로 지정된 변수)
블록 내부에 한정 (블록 내부 로컬 변수)
특정 스레드 한정 (다른 스레드로는 넘겨주지 않는 객체 e.g. ThreadLocal)

한정된 객체는 제한된 범위를 벗어나선 안된다. 이에 개발자가 충분한 주의를 기울여야 한다.

인스턴스 한정 기법은 스레드 안전성을 확보할 수 있는 가장 쉬운 방법, 동기화 적용 방법도 마음대로 선택 가능 (여러 데이터를 여러 락을 사용해 따로 동기화 하는 등)

스레드 안전성을 확보하는 방법으로 데코레이터 패턴 활용 가능 이러한 래퍼 클래스가 스레드 안전성을 확보하게 된다.

자바 모니터 패턴

자바 모니터 패턴 = 변경가능한 데이터를 객체 내부에 숨기고 락으로 데이터에 대한 동시 접근을 막는 방법
클래스를 바닥부터 새로 만들거나 이미 만들어져 있지만 스레드 안전성이 없는 개체를 조합해 만들때 유용하다.

스레드 안전성 위임 (aka. 위임 기법)

이미 스레드 안전한 클래스를 조합해서 사용한다고 해도 스레드에 안전하지 않을 수 있다. 주의할 것

케이스 분석
스레드 안전한 상태 하나만 관리할 때 
  = 해당 상태 객체에 스레드 안전을 위임하며 별다른 처리 없이도 클래스가 안전하다.

스레드 안전한 상태를 둘 이상 관리하는데 이 둘이 독립적일 때 
  = 각 객체에 스레드 안전을 위임하여 별다른 처리 없이도 클래스 안전하다.

스레드 안전한 상태가 둘 이상이지만 이 둘이 서로 상관관계가 있을 때 
  = 명시적 동기화를 통해서 두 변수 사이의 상태를 조율해야한다. 전체적으로는 스레드 안전성을 잃을 수 있다.

내부 상태 변수를 외부에 공개

공개하려는 상태에 따라 다르다.

상태 변수가 스레드 안전하고, 클래스 내부에서 해당 값에 대한 의존성이 없고, 상태 변수에 대한 어떤 연산이 잘못된 상태를 야기할 수 없는 경우 제외

스레드 안전하게 구현된 클래스에 기능 추가

필요한 기능을 구현해 추가하면서 스레드 안전성도 계속해서 유지하는 방법을 찾아야 한다.

클래스 재구성 (composition)

자바 모니터 패턴 처럼 동기화를 래핑해서 활용하는 방법으로 성능상 부정적일 수 있지만
그 영향을 엄밀히 따저야한다.

동기화 정책 문서화하기

문서를 남기는 건 스레드 안전성을 관리하는 가장 강력한 방법 안전성을 해치지 않도록 동기화 전략을 파악할 때 동기화 관련 개발 문서를 가장 먼저 참조해야한다. 동기화 정책을 정의하는게 중요하다. (synchronized, volatile 혹은 여러 동기화 관련 클래스 사용 등)

설계 단계에서 스레드 안전성도 함께 다루자.

어떤 변수를 어떤 락으로 막을지
어떤 변수를 불변 클래스로 만들지
어떤 변수를 어떤 스레드에 한정시킬지
어떤 연산을 단일 연산으로 만들어야 할 지

이러한 동기화 기법은 외부에도 영향을 미치기 때문에 스레드 안전성을 어디까지 보장하는지 문서로 남겨야한다.

Last updated