자바에서 데드락은 두 개 이상의 스레드가 영원히 블록된 프로그래밍 상황입니다. 자바 데드락 상황은 적어도 두 개의 스레드와 두 개 이상의 리소스가 필요합니다. 여기에서는 자바 데드락 시나리오를 발생시키는 간단한 프로그램을 작성하고 그것을 분석하는 방법을 살펴보겠습니다.
자바에서의 데드락
자바 스레드에서 데드락을 생성하는 간단한 프로그램을 살펴보겠습니다.
package com.journaldev.threads;
public class ThreadDeadlock {
public static void main(String[] args) throws InterruptedException {
Object obj1 = new Object();
Object obj2 = new Object();
Object obj3 = new Object();
Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");
Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3");
t1.start();
Thread.sleep(5000);
t2.start();
Thread.sleep(5000);
t3.start();
}
}
class SyncThread implements Runnable{
private Object obj1;
private Object obj2;
public SyncThread(Object o1, Object o2){
this.obj1=o1;
this.obj2=o2;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + " acquiring lock on "+obj1);
synchronized (obj1) {
System.out.println(name + " acquired lock on "+obj1);
work();
System.out.println(name + " acquiring lock on "+obj2);
synchronized (obj2) {
System.out.println(name + " acquired lock on "+obj2);
work();
}
System.out.println(name + " released lock on "+obj2);
}
System.out.println(name + " released lock on "+obj1);
System.out.println(name + " finished execution.");
}
private void work() {
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
위의 프로그램에서 SyncThread는 Runnable 인터페이스를 구현하고 synchronized 블록을 사용하여 하나씩 각각의 객체에 대해 잠금을 얻습니다. main 메서드에서는 SyncThread에 대해 실행 중인 세 개의 스레드가 있으며 각 스레드 간에 공유 리소스가 있습니다. 스레드는 첫 번째 객체에 대한 잠금을 획득할 수 있지만 두 번째 객체에 대한 잠금을 획득하려고 할 때 이미 다른 스레드에 의해 잠겨 있기 때문에 대기 상태로 전환됩니다. 이로 인해 스레드 간에 리소스에 대한 순환 종속성이 형성되어 데드락이 발생합니다. 위의 프로그램을 실행하면 다음과 같은 출력이 생성됩니다. 그러나 데드락으로 인해 프로그램이 종료되지 않습니다.
t1 acquiring lock on java.lang.Object@6d9dd520
t1 acquired lock on java.lang.Object@6d9dd520
t2 acquiring lock on java.lang.Object@22aed3a5
t2 acquired lock on java.lang.Object@22aed3a5
t3 acquiring lock on java.lang.Object@218c2661
t3 acquired lock on java.lang.Object@218c2661
t1 acquiring lock on java.lang.Object@22aed3a5
t2 acquiring lock on java.lang.Object@218c2661
t3 acquiring lock on java.lang.Object@6d9dd520
여기에서는 출력에서 데드락 상황을 명확히 식별할 수 있지만 실제 애플리케이션에서는 데드락 상황을 찾고 디버깅하는 것이 매우 어렵습니다.
자바에서 데드락을 감지하는 방법
자바에서 데드락을 감지하려면 애플리케이션의 자바 스레드 덤프를 살펴봐야 합니다. 이전 게시물에서는 VisualVM 프로파일러를 사용하거나 jstack
유틸리티를 사용하여 스레드 덤프를 생성하는 방법을 설명했습니다. 다음은 위 프로그램의 스레드 덤프입니다.
2012-12-27 19:08:34
Full thread dump Java HotSpot(TM) 64-Bit Server VM (23.5-b02 mixed mode):
"Attach Listener" daemon prio=5 tid=0x00007fb0a2814000 nid=0x4007 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"DestroyJavaVM" prio=5 tid=0x00007fb0a2801000 nid=0x1703 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"t3" prio=5 tid=0x00007fb0a204b000 nid=0x4d07 waiting for monitor entry [0x000000015d971000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
- waiting to lock <0x000000013df2f658> (a java.lang.Object)
- locked <0x000000013df2f678> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:722)
"t2" prio=5 tid=0x00007fb0a1073000 nid=0x4207 waiting for monitor entry [0x000000015d209000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
- waiting to lock <0x000000013df2f678> (a java.lang.Object)
- locked <0x000000013df2f668> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:722)
"t1" prio=5 tid=0x00007fb0a1072000 nid=0x5503 waiting for monitor entry [0x000000015d86e000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
- waiting to lock <0x000000013df2f668> (a java.lang.Object)
- locked <0x000000013df2f658> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:722)
"Service Thread" daemon prio=5 tid=0x00007fb0a1038000 nid=0x5303 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" daemon prio=5 tid=0x00007fb0a1037000 nid=0x5203 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" daemon prio=5 tid=0x00007fb0a1016000 nid=0x5103 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" daemon prio=5 tid=0x00007fb0a4003000 nid=0x5003 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" daemon prio=5 tid=0x00007fb0a4800000 nid=0x3f03 in Object.wait() [0x000000015d0c0000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000013de75798> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
- locked <0x000000013de75798> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:177)
"Reference Handler" daemon prio=5 tid=0x00007fb0a4002000 nid=0x3e03 in Object.wait() [0x000000015cfbd000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000013de75320> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:503)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
- locked <0x000000013de75320> (a java.lang.ref.Reference$Lock)
"VM Thread" prio=5 tid=0x00007fb0a2049800 nid=0x3d03 runnable
"GC task thread#0 (ParallelGC)" prio=5 tid=0x00007fb0a300d800 nid=0x3503 runnable
"GC task thread#1 (ParallelGC)" prio=5 tid=0x00007fb0a2001800 nid=0x3603 runnable
"GC task thread#2 (ParallelGC)" prio=5 tid=0x00007fb0a2003800 nid=0x3703 runnable
"GC task thread#3 (ParallelGC)" prio=5 tid=0x00007fb0a2004000 nid=0x3803 runnable
"GC task thread#4 (ParallelGC)" prio=5 tid=0x00007fb0a2005000 nid=0x3903 runnable
"GC task thread#5 (ParallelGC)" prio=5 tid=0x00007fb0a2005800 nid=0x3a03 runnable
"GC task thread#6 (ParallelGC)" prio=5 tid=0x00007fb0a2006000 nid=0x3b03 runnable
"GC task thread#7 (ParallelGC)" prio=5 tid=0x00007fb0a2006800 nid=0x3c03 runnable
"VM Periodic Task Thread" prio=5 tid=0x00007fb0a1015000 nid=0x5403 waiting on condition
JNI global references: 114
Found one Java-level deadlock:
=============================
"t3":
waiting to lock monitor 0x00007fb0a1074b08 (object 0x000000013df2f658, a java.lang.Object),
which is held by "t1"
"t1":
waiting to lock monitor 0x00007fb0a1010f08 (object 0x000000013df2f668, a java.lang.Object),
which is held by "t2"
"t2":
waiting to lock monitor 0x00007fb0a1012360 (object 0x000000013df2f678, a java.lang.Object),
which is held by "t3"
Java stack information for the threads listed above:
===================================================
"t3":
at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
- waiting to lock <0x000000013df2f658> (a java.lang.Object)
- locked <0x000000013df2f678> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:722)
"t1":
at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
- waiting to lock <0x000000013df2f668> (a java.lang.Object)
- locked <0x000000013df2f658> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:722)
"t2":
at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
- waiting to lock <0x000000013df2f678> (a java.lang.Object)
- locked <0x000000013df2f668> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:722)
Found 1 deadlock.
스레드 덤프 출력에서 데드락 상황과 데드락 상황을 일으키는 스레드와 리소스가 명확히 나타납니다. 데드락을 분석하기 위해서는 상태가 BLOCKED인 스레드와 그것이 잠금을 기다리는 리소스를 찾아야 합니다. 모든 리소스에는 객체에 대한 잠금을 이미 보유한 스레드를 찾을 수 있는 고유한 ID가 있습니다. 예를 들어 스레드 “t3″는 0x000000013df2f658을 잠금을 기다리고 있지만 이미 스레드 “t1″에 의해 잠겨 있습니다. 데드락 상황을 분석하고 데드락을 일으키는 스레드를 찾은 후에는 데드락 상황을 피하기 위해 코드를 변경해야 합니다.
자바에서 데드락을 피하는 방법
이러한 몇 가지 지침을 사용하여 대부분의 데드락 상황을 피할 수 있습니다.
-
중첩 락 피하기: 이는 교착 상태의 가장 일반적인 이유이며, 이미 하나의 리소스를 보유한 경우 다른 리소스를 잠그지 않도록하십시오. 하나의 객체 락만 사용하는 경우 교착 상태를 거의 발생시키기가 거의 불가능합니다. 예를 들어, 중첩 락이 없는 run() 메서드의 다른 구현이 있고 교착 상태 없이 프로그램이 성공적으로 실행되는 경우가 있습니다.
public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " acquiring lock on " + obj1); synchronized (obj1) { System.out.println(name + " acquired lock on " + obj1); work(); } System.out.println(name + " released lock on " + obj1); System.out.println(name + " acquiring lock on " + obj2); synchronized (obj2) { System.out.println(name + " acquired lock on " + obj2); work(); } System.out.println(name + " released lock on " + obj2); System.out.println(name + " finished execution."); }
-
필요한 것만 잠그세요: 당신은 작업해야 하는 리소스에만 잠금을 획득해야 합니다. 예를 들어 위의 프로그램에서 전체 객체 리소스를 잠그고 있지만, 만약 하나의 필드에만 관심이 있다면 해당 필드만 잠그세요. 전체 객체를 잠그지 말아야 합니다.
-
무기한 대기 피하세요: 두 스레드가 서로 무기한 대기하고 있다면, 스레드 조인을 사용하여 교착 상태에 빠질 수 있습니다. 스레드가 다른 스레드가 완료될 때까지 기다려야 하는 경우, 스레드가 완료될 때까지 기다리고자 하는 최대 시간과 함께 조인을 사용하는 것이 항상 좋습니다.
자바 스레드에서의 교착 상태에 관한 내용은 여기까지입니다.
Source:
https://www.digitalocean.com/community/tutorials/deadlock-in-java-example