교착상태(Deadlock)와 라이브락(Livelock)은 둘 다 다중 스레드나 프로세스가 상호작용하는 시스템에서 발생할 수 있는 문제입니다. 각각의 개념을 설명하고, 발생 원인 및 예시를 들어 이해를 돕겠습니다.
교착상태는 두 개 이상의 프로세스가 서로의 작업이 끝나기를 무한정 기다리며 아무런 작업도 진행하지 못하는 상태를 의미합니다. 이 상태에서는 각 프로세스가 다른 프로세스가 소유한 자원을 기다리기 때문에 진행이 불가능해집니다.
두 스레드가 두 자원을 사용하려고 할 때 교착상태가 발생할 수 있습니다. 예를 들어, 두 스레드가 각각 자원 A와 자원 B를 필요로 하는 상황을 생각해봅시다.
public class DeadlockExample {
static class Resource {
private final String name;
Resource(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public static void main(String[] args) {
final Resource resource1 = new Resource("Resource1");
final Resource resource2 = new Resource("Resource2");
Runnable task1 = () -> {
synchronized (resource1) {
System.out.println(Thread.currentThread().getName() + " locked " + resource1.getName());
try { Thread.sleep(50); } catch (InterruptedException e) {}
synchronized (resource2) {
System.out.println(Thread.currentThread().getName() + " locked " + resource2.getName());
}
}
};
Runnable task2 = () -> {
synchronized (resource2) {
System.out.println(Thread.currentThread().getName() + " locked " + resource2.getName());
try { Thread.sleep(50); } catch (InterruptedException e) {}
synchronized (resource1) {
System.out.println(Thread.currentThread().getName() + " locked " + resource1.getName());
}
}
};
new Thread(task1, "Thread-1").start();
new Thread(task2, "Thread-2").start();
}
}
resource1을 잠그고, 잠시 후에 resource2를 잠그려고 합니다.resource2를 잠그고, 잠시 후에 resource1을 잠그려고 합니다.resource2가 Thread-2에 의해 잠겨 있어서 기다려야 하고, 동시에 Thread-2는 resource1이 Thread-1에 의해 잠겨 있어서 기다려야 합니다.라이브락은 두 개 이상의 프로세스가 서로 상대방이 진행할 수 있도록 양보하다가 무한정 대기 상태에 빠지는 것을 의미합니다. 이 경우 각 프로세스는 계속해서 상태를 바꾸지만, 유효한 작업을 수행하지 못합니다.
두 스레드가 자원을 사용하려고 계속 양보하는 상황을 생각해봅시다.
public class LivelockExample {
static class Resource {
private final String name;
private boolean inUse;
Resource(String name) {
this.name = name;
}
public synchronized void use() {
while (inUse) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
inUse = true;
}
public synchronized void release() {
inUse = false;
notify();
}
public String getName() {
return name;
}
}
public static void main(String[] args) {
final Resource resource1 = new Resource("Resource1");
final Resource resource2 = new Resource("Resource2");
Runnable task1 = () -> {
while (true) {
resource1.use();
System.out.println(Thread.currentThread().getName() + " using " + resource1.getName());
try { Thread.sleep(50); } catch (InterruptedException e) {}
if (!resource2.inUse) {
resource2.use();
System.out.println(Thread.currentThread().getName() + " using " + resource2.getName());
resource1.release();
resource2.release();
break;
}
resource1.release();
try { Thread.sleep(10); } catch (InterruptedException e) {}
}
};
Runnable task2 = () -> {
while (true) {
resource2.use();
System.out.println(Thread.currentThread().getName() + " using " + resource2.getName());
try { Thread.sleep(50); } catch (InterruptedException e) {}
if (!resource1.inUse) {
resource1.use();
System.out.println(Thread.currentThread().getName() + " using " + resource1.getName());
resource2.release();
resource1.release();
break;
}
resource2.release();
try { Thread.sleep(10); } catch (InterruptedException e) {}
}
};
new Thread(task1, "Thread-1").start();
new Thread(task2, "Thread-2").start();
}
}
resource1을 사용하려고 하지만 resource2가 사용 중인지 확인합니다.resource2가 사용 중이면 resource1을 해제하고 잠시 대기합니다.resource2를 사용하려고 하지만 resource1이 사용 중인지 확인합니다.resource1이 사용 중이면 resource2를 해제하고 잠시 대기합니다.이 두 가지 문제는 다중 스레드 환경에서 발생할 수 있으며, 적절한 설계와 같은 동기화 기법또는 ThreadLocal 등을 통해 방지할 수 있습니다.