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