활동성을 최대로 높이기

데드락

  • 데드락이 걸린 애플리케이션을 정상 상태로 되돌리려면 종료 후 재시작하는 수밖에 없다.

  • 자바 애플리케이션에는 데드락을 자동으로 감지하고 빠져나오게 하는 매커니즘을 제공하지 않는다.

발생 케이스

  • 락 순서에 의한 데드락

    • 문제

      • 두 개의 스레드가 서로 다른 순서로 동일한 락들을 확보하려 할 때 데드락이 발생할 수 있다.

      • 여러 스레드가 락을 확보해야 할 때 모두 동일한 순서로 획득하는 경우 문제가 없다.

      • 예를 들어 X 계좌 -> Y 계좌로 송금 하려는 요청Y 계좌 -> X 계좌로 송금하려는 요청이 동시에 발생하였고 각각 첫 계좌부터 락을 잡기 시작한다면, 서로 두 번째 계좌의 락을 확보하지 못해 데드락이 발생할 것이다.

    • 해결 방안

      • identityHashCode 메서드를 통해 두 객체 중 hashcode값이 작은 객체에 대한 락을 먼저 잡고 그 다음 객체에 대한 락을 잡도록 하여, 전역적으로 일관된 순서로 락을 잡도록 보장할 수 있다. 하지만 드문 경우에 hashcode 값이 동일하면서 데드락이 발생할 수 있다.

      • hashcode 값이 동일한 경우에는 여러 락을 임의의 순서로 확보하는 작업 자체에 대해 타이 브레이킹 락을 확보하여 한 번에 한 스레드만 여러 락을 잡을 수 있도록 할 수 있다.

      • 유일하면서 불변인 값을 기준으로 락 순서를 일정하게 지정한다면 데드락을 방지할 수 있다.

  • 객체 간의 데드락

    • 문제

      • synchronized 메서드를 가지는 A, B 클래스가 있고, 서비스 클래스에서 A 객체의 synchronized 메서드 호출 -> B 객체의 synchronized 메서드 호출 하는 메서드와 B 객체의 synchronized 메서드 호출 -> A 객체의 synchronized 메서드 호출 하는 메서드를 제공하여 동시에 호출된다면 데드락이 발생할 수 있다.

      • 메서드를 호출할 때 내부에서 어떤 일이 일어나는지 모르므로, 특정 락을 확보한 상태에서 에일리언 메서드를 호출하면, 이미 확보한 락을 다른 스레드가 계속해서 대기해야 하는 경우가 발생할 수도 있다.

    • 해결 방안

      • 락을 확보한 상태에서 에일리언 메서드를 호출하는 경우가 있는지 확인하여 방지할 수 있다.

  • 오픈 호출

    • 락을 전혀 확보하지 않은 상태에서 메서드를 호출하여 데드락을 미연에 방지하는 방식이다.

    • 항상 오픈 호출을 사용한다고 가정하면 여러 개의 락을 사용하는 프로그램의 실행 경로를 쉽게 확인할 수 있고, 항상 일정한 순서로 락을 확보하도록 만들기 쉽다.

    • 기존에 synchronized 메서드를 사용하다가 synchronized 블록의 구조를 변경해 오픈 호출 방법을 사용하게 되면, 단일 연산으로 실행되던 코드가 여러 단위로 나뉘어 실행되므로 연산의 단일성을 잃을 수 있다.

    • 오픈 호출된 이후 실행될 코드를 한 번에 하나씩만 실행되도록 객체의 구조를 정의할 수 있다. 예를 들어 서비스를 종료하는 과정에서 새로운 작업을 시작하지 않도록 종료중임을 나타내는 변수를 객체에서 공유하여 사용할 수 있다.

  • 리소스 데드락

    • 필요한 자원을 사용하기 위해 대기하는 과정에서 데드락이 발생할 수 있다.

    • 예를 들어 A, B 데이터베이스의 연결을 서로 다른 순서로 필요로 하는 두 메서드가 동시에 호출되는 경우, 한 데이터베이스에 대한 연결은 얻었지만 나머지 데이터베이스에 대한 연결을 얻을 수 없어 멈출 수 있다.

    • 단일 스레드로 동작하는 Executor에서 현재 실행중인 작업이 또 다른 작업을 Executor에 할당하고 끝나기를 대기하는 경우, 현재 작업이 영원히 종료되지 않는다.

데드락 방지 및 원인 추적

데드락 방지

  • 한 번에 하나 이상의 락을 사용하지 않도록 구현하면, 락의 순서에 의한 데드락이 발생하지 않는다.

  • 여러 개의 락을 사용해야 한다면 락을 사용하는 순서를 설계 단계에서 충분히 고려해야 한다.

  • Lock 클래스의 tryLock 메서드를 사용하여 일정 시간동안만 락 확보를 위해 대기하는 것이 좋다. 락을 확보하려 했지만 실패한 경우, 이미 확보했던 락이 있다면 풀어주고 로그를 남긴 후 나중에 재시도하여 해결할 수 있다.

  • 데드락이 발생했다면 스레드 덤프를 활용해 데드락이 발생한 위치를 확인할 수 있다. JVM은 스레드 덤프를 생성하기 전 락 대기 상태 그래프에서 사이클이 발생했는지 확인하여 데드락이 있었다고 판단되면 어느 부분이 데드락에 관여하고 있는지 락 확보 규칙을 깨고있는 지에 대한 상세 정보를 스레드 덤프에 함께 담는다.

소모

  • Starvation

  • 스레드가 작업을 진행하는 데에 필요한 자원을 영원히 할당받지 못하는 경우 발생한다.

  • 대부분 스레드의 우선 순위를 적절하지 못하게 올리거나 내리면서 발생하게 된다.

  • 락을 확보한 채로 종료되지 않는 코드가 실행될 때에도 마찬가지로 소모가 발생할 수 있다.

  • 자바의 스레드 API는 10단계의 스레드 우선 순위를 지정할 수 있으며 JVM은 해당 우선 순위를 하위 운영체제의 스케줄링 우선 순위에 적절히 대응시킨다.

  • 플랫폼마다 스레드 우선 순위를 운영체제의 우선 순위에 대응시키는 방식이 다르므로 특정 시스템에는 여러 스레드의 우선 순위가 동일한 값으로 지정되고, 다른 시스템에서는 다른 값으로 지정될 수 있다.

  • 일반적인 상황에서는 스레드 우선 순위를 변경하지 않고 그대로 사용하는 것이 좋다.

라이브락

  • 특정 작업의 결과를 받아야 다음 단계로 넘어가는 작업이 실패할 수 밖에 없는 기능을 계속해서 재시도하는 경우 발생한다.

  • 에러를 완벽하게 처리하고자 하여 회복 불가능한 오류를 회복 가능하다고 간주하여 계속 재시도하면 영원히 해결되지 않을 수 있다.

  • 이 경우 재시도하는 텀을 임의로 지정하여 해결할 수 있다.

Last updated