반응형
10.1 데드락
자바 프로그램에서 데드락이 발생하면 프로그램을 강제로 종료하기 전에는 영원히 멈춘 상태로 유지된다.
10.1.1 락 순서에 의한 데드락
예제 코드는 데드락이 발생할 여지가 있다.
public class LeftRightDeadlock {
private final Object left = new Object();
private final Object right = new Object();
public void leftRight() {
synchronized(left) {
synchronized(right) {
doSomething();
}
}
}
public void rightLeft() {
synchronized(right){
synchronized(left) {
doSomething();
}
}
}
}
프로그램 내부의 모든 스레드에서 필요한 락을 모두 같은 순서로만 사용한다면 락 순서에 의한 데드락은 발생하지 않는다.
10.1.2 동적인 락 순서에 의한 데드락
public void transferMoney(Account fromAccount, Account toAccount, DollarAmount amount)
throws InsufficientFundsException {
synchronized (fromAccount) {
synchronized (toAccount) {
if (fromAccount.getBalance().compareTo(amount) < 0)
throw new InsufficientFundsException();
else {
fromAccount.debit(amount);
toAccount.credit(amount);
}
}
}
}
transferMoney(myAccount, yourAccount, 10);
transferMoney(yourAccount, myAccount, 20);
위 처럼 호출시 데드락에 빠질 여지가 있다.
락의 순서를 프로그램으로 제어할 수 있다면 데드락을 방지할 수 있다.
System.identityHashCode 메소드를 사용하여 객체의 순서를 정의해 락을 걸어주면 데드락을 방지할 수 있다. 만일에라도 객체의 순서가 같을 경우를 대비해 tieLock 을 사용한다.
private static final Object tieLock = new Object();
public void transferMoney(final Account fromAcct, final Account toAcct, final DollarAmount amount)
throws InsufficientFundsException {
class Helper {
public void transfer() throws InsufficientFundsException {
if (fromAcct.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException();
} else {
fromAcct.debit(amount);
toAcct.credit(amount);
}
}
}
int fromHash = System.identityHashCode(fromAcct);
int toHash = System.identityHashCode(toAcct);
if (fromHash < toHash) {
synchronized (fromHash) {
synchronized (toAcct) {
new Helper().transfer();
}
}
} else if (fromHash > toHash) {
synchronized (toAcct) {
synchronized (fromAcct) {
new Helper().transfer();
}
}
} else {
synchronized (tieLock) {
synchronized (fromAcct) {
synchronized (toAcct) {
new Helper.transfer();
}
}
}
}
}
10.1.3 객체 간의 데드락
Taxi 의 setLocation 메소드에서는 Taxi, Dispatcher 의 순서로 락을 획득하며
Dispatcher 의 getImage 메소드에서는 Dispatcher, Taxi 의 순서로 락을 획득한다.
따라서 데드락에 빠질 위험이 있다.
class Taxi {
@GuardedBy("this")
private Point location, destination;
private final Dispatcher dispatcher;
public Taxi(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
}
public synchronized Point getLocation() {
return location;
}
public synchronized void setLocation(Point location) {
this.location = location;
if (location.equals(destination)) {
dipatcher.notifyAvailable(this);
}
}
}
class Dispatcher {
@GuardedBy("this")
private final Set taxis;
@GuardedBy("this")
private final Set availableTaxis;
public Dispatcher() {
taxis = new HashSet();
availableTaxis = new HashSet();
}
public synchronized void notifyAvailable(Taxi taxi) {
availableTaxis.add(taxi);
}
public synchronized Image getImage() {
Image image = new Image();
for (Taxi t : taxis) {
image.drawMarker(t.getLocation());
}
return image;
}
}
public void setLocation(Point location) {
boolean reachedDestination;
synchronized (this) {
this.location = location;
reachedDestination = location.equals(destination);
}
if (reachedDestination) {
dipatcher.notifyAvailable(this);
}
}
public Image getImage() {
Set copy;
sychronized(this) {
copy = new HashSet(taxis);
}
Image image = new Image();
for (Taxi t : copy) {
image.drawMarker(t.getLocation());
}
return image;
}
10.1.4 오픈 호출 open call
- 오픈 호출은 락을 전혀 확보하지 않은 상태에서 메소드를 호출하는 것을 말한다.
- 메소드 호출이 모두 오픈 호출로 이루어진 클래스는 락을 확보한 채 메소드 호출하는 클래스 보다 안전, 분석 작업이 간편함
10.1.5 리소스 데드락
- 두 개의 데이터 베이스에 대한 커넥션 풀을 사용할 때 두개의 커넥션을 모두 요청하는 경우
- A 스레드는 D1, D2 의 순서로 커넥션을 요청하고 B 스레드는 D2, D1 의 순서로 요청하는 경우
- 커넥션풀의 크기가 클수록 문제가 발생할 확률은 줄어들지만 발생할 여지는 존재한다.
10.2 데드락 방지 및 원인 추적
한번에 하나 이상의 락을 사용하지 않는 프로그램은 락의 순서에 의한 데드락이 발생하지 않는다.
10.2.1 락의 시간 제한
- 암묵적인 락은 락을 확보할 때까지 영원히 기다림
- Lock 클래스의 메소드 가운데 시간을 제한할 수 있는 tryLock 메소드를 사용하면 지정한 시간 또한 락을 확보하지 못한다면 tryLock 메소드가 오류를 발생시키도록 할 수 있다.
10.2.2 스레드 덤프를 활용한 데드락 분석
- 스레드 덤프에는 실행 중인 모든 스레드의 스택 트레이스 stack trace 가 담겨 있다. 락과 관련된 정보 또한 담겨 있다.
- JVM이 스레드 덤프를 생성하도록 하려면 Unix 플랫폼에서는 JVM 프로세스에 SIGQUIT 시그널(kill -3)을 전송하거나 Ctrl-\ 키를 누르면 되고, 윈도우 환경에서는 Ctrl-Break 키를 누르면 된다.
10.3 그 밖의 활동성 문제
활동성을 떨어뜨리는 주된 원인은 데드락이지만, 소모 starvation, 놓친 신호, 라이브락, livelock 같은 다양한 원인이 존재한다.
10.3.1 소모
- 소모 starvation 상태는 스레드가 작업을 진행하는데 꼭 필요한 자원(주로 CPU)을 영영 할당 받지 못하는 경우에 발생한다.
- 소모 상황이 발생하는 원인은 대부분 스레드의 우선 순위 priority 를 적절치 못하게 올리거나 내리는 부분에 있다. 또는 락을 확보한 채로 종료되지 않는 코드.
- 스레드 우선 순위를 변경하고 나면 플랫폼에 종속적인 부분이 많아지며, 따라서 활동성 문제를 일으키기 쉽다.
- 일반적인 상황에서는 우선 순위를 변경하지 않는 것이 현명함.
10.3.2 형편 없는 응답성
- 애플리케이션의 응답성이 떨어진다면 락을 제대로 관리하지 못하는 것이 원인일 수 있다.
- 특정 스레드가 대량의 데이터를 넘겨 받아 처리하느라 필요 이상으로 긴 시간 동안 락을 확보하고 있다면 넘겨준 대량의 데이터를 사용해야 하는 다른 스레드는 데이터를 받아올 때까지 상당히 긴 시간 동안 대기해야 한다.
10.3.3 라이브락
- 무한 반복하는 경우를 말함
- 라이브락은 에러를 너무 완벽하게 처리하고자 회복 불가능한 오류를 회복 가능하다고 판단해 계속해서 재시도하는 과정에 나타난다.
- 라이브락 해결을 위해 재시도 시 약간의 변형을 넣는 방법이 있음 (예, 이더넷 프로토콜에서 신호 충돌 시 임의의 시간 대기 후 재시도)
반응형
'개발 > 병렬 프로그래밍' 카테고리의 다른 글
자바 병렬 프로그래밍 - 13장 명시적인 락 (0) | 2016.09.07 |
---|---|
자바 병렬 프로그래밍 - 11장 성능, 확장성 (0) | 2016.09.04 |
자바 병렬 프로그래밍 - 8장 스레드 풀 활용 (0) | 2016.08.30 |
자바 병렬 프로그래밍 - 7장 중단 및 종료 (0) | 2016.08.29 |
자바 병렬 프로그래밍 - 6장 작업실행 (0) | 2016.08.16 |