자바 - 스레드 데드락
안녕하세요, 미래의 자바 마법사 여러분! 오늘은 자바 프로그래밍에서 가장 까다로운 개념 중 하나를 탐구하려고 합니다: 스레드 데드락. 두려워 마세요 - 이 수업이 끝날 때까지 여러분은 데드락 탐정이 되어, 이러한 문제를 전문가처럼 발견하고 해결할 수 있을 것입니다!
스레드 데드락이란 무엇인가요?
저녁 식회에 가서 덤벼야 하는데, 포크와 날이 모두 필요합니다. 포크를 들었지만, 날을 잡으려고 했을 때 친구가 이미 날을 들고 있습니다. 동시에 친구도 여러분의 포크를 들어 덤벼야 하지만, 여러분은 날을 잡을 때까지 포크를 놓지 않습니다. 둘 다 서로가 필요한 것을 놓치기를 기다리고 있습니다. 이것이, 친구들아, 현실 생활에서의 데드락입니다!
자바에서는 두 개 이상의 스레드가 영원히 차단되고, 서로가 해제할 리소스를 기다리는 경우 데드락이 발생합니다. 마이크스 톱스타일의 상황이지만, 자바 스레드가 카우보이 대신에 있습니다!
스레드와 동기화 이해하기
데드락에 더 깊이 들어가기 전에, 몇 가지 주요 개념을 빠르게 검토해 봅시다:
스레드
스레드는 프로그램의 작은 작업자로, 각각 특정 작업을 합니다. 그들은 동시에 작업할 수 있어 프로그램이 더 효율적일 수 있습니다.
동기화
동기화는 한 번에 하나의 스레드만 공유 자원에 접근할 수 있도록 하는 방법입니다. 호텔 방 문에 "방해 금지" 표지를 걸어 두는 것과 같습니다.
데드락이是如何发生的
데드락은 주로 네 가지 조건(코프만 조건으로 알려져 있습니다)이 충족될 때 발생합니다:
- 상호 배제: 적어도 하나의 자원이 비공유 모드로 보관되어야 합니다.
- 보유하고 기다리기: 스레드는 다른 스레드가 보관하고 있는 추가 자원을 취득하기 위해 기다리는 동안 적어도 하나의 자원을 보관해야 합니다.
- 사전 절대: 자원은 강제로 스레드에서 가져올 수 없고, 자발적으로 해제되어야 합니다.
- 순환 기다리기: 두 개 이상의 스레드가 서로에게 순환적으로 기다리는 체인이 형성됩니다.
예제: 데드락 상황 보여주기
클래식한 데드락 상황을 살펴보겠습니다. 두 개의 자원(오브젝트로 표현됩니다)와 이러한 자원을 다른 순서로 취득하려는 두 개의 스레드를 만듭니다.
public class DeadlockExample {
private static Object resource1 = new Object();
private static Object resource2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 1: Holding Resource 1...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread 1: Waiting for Resource 2...");
synchronized (resource2) {
System.out.println("Thread 1: Holding Resource 1 and Resource 2");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (resource2) {
System.out.println("Thread 2: Holding Resource 2...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread 2: Waiting for Resource 1...");
synchronized (resource1) {
System.out.println("Thread 2: Holding Resource 2 and Resource 1");
}
}
});
thread1.start();
thread2.start();
}
}
이것을 분석해 봅시다:
- 두 개의
Object
인스턴스resource1
과resource2
를 만들어 공유 자원으로 사용합니다. - 두 개의 스레드를 만듭니다:
-
thread1
은 먼저resource1
을 취득하고, 그런 다음resource2
를 취득하려고 합니다. -
thread2
는 먼저resource2
을 취득하고, 그런 다음resource1
을 취득하려고 합니다.
- 두 스레드는
synchronized
키워드를 사용하여 자원을 잠그습니다. - 작은 지연(
Thread.sleep(100)
)을 추가하여 데드락이 발생할 가능성을 높입니다.
이 코드를 실행하면 데드락이 발생할 가능성이 큽니다. 스레드 1은 resource1
을 취득하고 resource2
를 기다리지만, 스레드 2는 resource2
를 취득하고 resource1
을 기다리게 됩니다. 어느 스레드도 진행할 수 없어 데드락이 발생합니다.
데드락 해결 예제
이제 데드락이是如何 발생했는지를 알았으니, 이를 어떻게 예방할 수 있는지 살펴보겠습니다. 간단한 해결책은 모든 스레드에서 자원을 항상 같은 순서로 취득하는 것입니다.
public class DeadlockSolutionExample {
private static Object resource1 = new Object();
private static Object resource2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 1: Holding Resource 1...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (resource2) {
System.out.println("Thread 1: Holding Resource 1 and Resource 2");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 2: Holding Resource 1...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (resource2) {
System.out.println("Thread 2: Holding Resource 1 and Resource 2");
}
}
});
thread1.start();
thread2.start();
}
}
이 해결책에서:
- 두 스레드는 모두 먼저
resource1
을 취득하고, 그런 다음resource2
를 취득합니다. - 이를 통해 자원 취득의 일관된 순서를 유지하여 순환 기다리기 조건을 예방합니다.
데드락 피하는 베스트 프랙티스
- 항상 같은 순서로 자원을 취득: 우리의 해결책 예제처럼, 순환 기다리기를 예방합니다.
- 중첩된 잠금을 피하고: 동기화 블록의 수를 최소화하려고 시도합니다.
-
tryLock()
을 사용하여 타임아웃 설정: 무기한으로 기다리는 대신,tryLock()
을 사용하여 특정 시간 동안 자원을 취득하려고 시도합니다. - 자원을 장시간 보관하지 마세요: 공유 자원을 사용한 후에는 빨리 자원을 해제합니다.
결론
축하합니다! 여러분은 자바 스레드 데드락의 비밀을 풀었습니다. 기억하십시오, 다중 스레드 프로그램을 작성하는 것은 복잡한 무대를 직접하는 것과 같습니다 - 신중한 계획과 협조가 필요하여 모든 무대를光滑하게 움직이게 하고, 서로의 발을 밟지 않도록 합니다.
자바 여정을 계속하면서, 이러한 개념을 기억하고 있으면 효율적이고 데드락이 없는 다중 스레드 프로그램을 작성할 준비가 될 것입니다. 코딩 잘 하세요, 여러분의 스레드가 항상 조화롭게 되길 바랍니다!
메서드 | 설명 |
---|---|
synchronized |
동기화 블록이나 메서드를 생성하는 데 사용되는 키워드 |
Object.wait() |
현재 스레드를 다른 스레드가 notify() 또는 notifyAll() 을 호출할 때까지 기다리게 합니다 |
Object.notify() |
이 객체의 모니터를 기다리는 단일 스레드를 깨웁니다 |
Object.notifyAll() |
이 객체의 모니터를 기다리는 모든 스레드를 깨웁니다 |
Thread.sleep(long millis) |
현재 실행 중인 스레드를 지정된 밀리초 동안 잠시 멈춥니다 |
Lock.tryLock() |
무료 상태일 때에만 잠금을 취득합니다 |
Lock.tryLock(long time, TimeUnit unit) |
지정된 대기 시간 동안 잠금을 취득하려고 시도합니다 |
ReentrantLock.lock() |
잠금을 취득합니다 |
ReentrantLock.unlock() |
잠금을 해제합니다 |
이 메서드들은 여러분의 다중 스레드 툴킷에 강력한 도구입니다. 현명하게 사용하면, 강력하고 효율적인 자바 애플리케이션을 쉽게 만들 수 있을 것입니다!
Credits: Image by storyset