활동성을 최대로 높이기
데드락
데드락이 걸린 애플리케이션을 정상 상태로 되돌리려면 종료 후 재시작하는 수밖에 없다.
자바 애플리케이션에는 데드락을 자동으로 감지하고 빠져나오게 하는 매커니즘을 제공하지 않는다.
발생 케이스
락 순서에 의한 데드락
문제
두 개의 스레드가 서로 다른 순서로 동일한 락들을 확보하려 할 때 데드락이 발생할 수 있다.
여러 스레드가 락을 확보해야 할 때 모두 동일한 순서로 획득하는 경우 문제가 없다.
예를 들어
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