2장: 스레드 안전성
스레드 안전성
여러 스레드 간에 공유되고 변경할 수 있는 객체의 상태에 대한 접근을 관리해야 한다.
여러 스레드가 클래스에 동시에 접근할 때 계속 정확하게 동작한다면 해당 클래스는 스레드 안전(thread-safe)하다고 부른다.
스레드 안전한 클래스는 클라이언트가 별도로 동기화할 필요가 없도록 동기화 기능을 캡슐화한다.
상태가 없는 객체는 항상 스레드 안전하다.
단일 연산
완전한 단일 연산이라면 스레드 안전할 수 있겠지만, 대부분의 연산은 내부적으로 여러 연산들이 합쳐진 복합 연산의 형태를 띤다.
따라서 이러한 경우 멀티 스레드 환경에서 안전하지 않다.
상대적인 시점 혹은 JVM이 여러 스레드를 교차해서 실행하는 상황에서 경쟁 조건이 발생한다.
check-then-act 형태의 연산은 check하고 act하기 전 그 사이에 다른 스레드가 동시에 접근할 수 있기 때문에, 경쟁 조건이 발생하는 가장 일반적인 형태이다.
락
자바에서 제공하는 synchronized 키워드를 통해 락을 사용할 수 있다.
자바 객체마다 제공해주는 락을 intrinsic lock / monitor lock이라고 부른다.
스레드가 synchronized 블록에 들어가기 전 자동으로 락이 확보되고, 해당 블록에서 벗어날 때 자동으로 락이 해제된다.
한 번에 한 스레드만 락을 소유 가능한 mutexes / mutual exclusive lock 락이다.
여러 복합 연산들을 락 내부에서 수행하면 단일 연산처럼 수행된다.
재진입성
특정 스레드는 자신이 이미 획득한 락을 다시 확보할 수 있다.
상속 구조의 클래스를 갖는 경우 하위 클래스가 synchronized 메서드를 오버라이드하고 내부적으로 상위 클래스의 synchronized 메서드를 호출하려 할 수 있다. 이 때 재진입성이 보장되지 않는다면, 동일 객체의 락을 다시 확보하지 못해 데드락에 빠지게 된다.
public class Widget { public synchronized void method1() { // ... } } public class LoggingWidget extends Widget { @Override public synchronized void method1() { // ... super.method1(); } }
여러 스레드에서 접근할 수 있고 변경 가능한 변수는 수정 뿐만 아니라 조회 등 모든 접근 시에 동일한 락을 확보해두어야 한다.
여러 변수에 대한 불변 조건이 있으면 해당 변수들은 모두 같은 락으로 보호되어야 한다.
활동성과 성능
단순하고 큰 단위의 모든 메서드를 동기화하면 활동성이나 성능에 문제가 생길 수 있다.
계산량이 많거나 잠재적으로 대기 상태에 들어갈 수 있는 작업은 락을 잡은 상태에서 수행하지 않는 것이 좋다.
이를 방지하기 위해 단일 연산으로 처리해야 하는 작업을 synchronized block으로 감싸 사용할 수 있다.
@GuardedBy
어노테이션을 통해 특정 변수가 락으로 보호되어 있다는 사실을 문서화할 수 있다.
Last updated