development

Java 스레드(Thread) 데드락(Deadlock) 방지하는 방법

여름싼타 2023. 2. 7. 07:00
반응형

 

자바에서 스레드 데드락을 방지하는 방법 정리

 

두 개 이상의 스레드가 서로 리소스 액세스를 완료하기를 기다릴 때마다 데드락이 발생한다. 데드락, 스레드 데드락의 원인 및 자바에서 스레드 데드락을 방지할 수 있는 방법에 대해 정리했다.

 

1) 데드락(Deadlock)이란

컴퓨팅에서 두 개 이상의 동시 작업이 서로 완료되기를 기다리는 경우 데드락이 발생한다. 즉, 두 스레드가 다른 스레드의 잠금 해제를 기다리기 때문에 서로를 영원히 차단할 때 데드락이 발생한다. 이러한 상황은 두 스레드가 리소스를 공유하고 둘 다 다른 스레드가 보유한 공유 리소스에 대한 잠금을 얻기 위해 대기할 때 종종 발생한다.

데드락이 발생하려면 다음 조건이 충족되어야 한다.

  • 최소한 하나의 리소스는 하나의 스레드만 동시에 액세스 할 수 있도록 상호 배타적(예: 뮤텍스(mutex))이어야 한다.
  • 보류 및 대기 : 쓰레드는 다른 리소스가 오기를 기다리는 동안 한 리소스를 보류해야 한다.
  • 선점 없음 : 스레드가 리소스를 획득한 후에는 리소스에 대한 잠금을 강제로 제거할 수 없다. (즉, 잠금을 선점할 수 없음).
  • 순환 대기 : 각 스레드는 순환 방식으로 다른 스레드에서 리소스를 기다려야 한다.

 

2) Java에서 데드락을 피하는 방법

Java는 동기화된 블록 사용, 스레드 안전 컬렉션 사용 및 원자적 작업 사용과 같은 스레드 교착 상태를 방지하기 위한 다양한 방법을 제공한다.

Thread.join() 사용

여러 가지 방법으로 Java의 데드락을 피할 수 있다. 우선 Thread.join() 메서드를 사용할 수 있다. Thread.join()을 사용하여 한 스레드가 다른 스레드를 시작하기 전에 완료되도록 할 수 있다. 예를 들어 한 스레드가 파일에서 읽고 다른 스레드가 동일한 파일에 쓰는 경우다. 따라서 데드락이 발생할 수 없다.

동기화 개체 사용

동기화 및 동기화 프리미티브를 사용하여 교착 상태를 피할 수 있다. 뮤텍스 또는 세마포어와 같은 동기화 개체를 사용하는 것은 교착 상태를 방지하는 또 다른 방법이다. 이는 동일한 리소스에 대한 잠금을 위해 경쟁하는 여러 스레드로 인해 발생하는 교착 상태로부터 보호한다.

Java에서 교착 상태를 방지하려면 동기화된 블록이 고정된 순서로 사용되는지 항상 확인해야한다.. 즉, 여러 스레드가 동일한 리소스에 액세스 하려고 하면 항상 동일한 순서로 리소스에 대한 잠금을 얻어야 한다. 또한 교착 상태를 방지하려면 중첩된 동기화 블록을 피하는 것이 중요하다.

중첩 잠금 방지

또한 개발자는 중첩된 잠금을 피함으로써, 즉 객체에 대한 잠금이 이미 획득되었을 때 다른 잠금을 획득하지 않음으로써 교착 상태를 피할 수 있다. 또한 잠금 획득을 위한 제한 시간 정책을 구현하고 여러 스레드에서 동일한 순서로 리소스에 액세스 하도록 하여 교착 상태 상황을 방지할 수 있다.

필요하지 않을 때 잠금을 사용하지 않는다

잠금은 절대적으로 필요한 경우에만 획득해야 하며 가능한 한 빨리 해제해야 한다. 스레드가 필요하지 않은 잠금을 획득하면 다른 스레드가 불필요하게 차단될 수 있다. 불필요한 잠금을 방지하려면 각 스레드가 액세스하는 리소스와 스레드가 보유한 잠금을 이해하는 것이 중요하다.

적절한 코드 설계

또한 교착 상태가 발생하지 않도록 코드를 설계할 수 있다. 또한 스레드 간에 순환 대기 종속성이 없도록 프로그램을 설계해야 한다. 스레드로부터 안전한 클래스 및 데이터 구조를 사용하여 Java 애플리케이션에서 스레드 교착 상태의 위험을 줄여야 한다.

여러 작업을 실행할 때 프로그래머는 지정된 순서로 일련의 하위 작업을 수행할 마스터 작업을 설정해야 한다. 이렇게 하면 두 스레드가 동시에 동일한 잠금을 획득하려고 시도하지 않도록 하여 교착 상태가 발생하지 않도록 할 수 있다.

 

 

3) Java 데드락 코드 예

다음 코드 예제는 Java의 데드락 상태를 보여준다.

public class MyThreadDeadlockDemo {

    public static Object lockObjectA = new Object();
    public static Object lockObjectB = new Object();
    
    public static void main(String args[]) {
        MyThreadClassA threadObjectA = new MyThreadClassA();
        MyThreadClassB threadObjectB = new MyThreadClassB();

        threadObjectA.start();
        threadObjectB.start();
    }

    private static class MyThreadClassA extends Thread {
        public void run() {
            synchronized(lockObjectA) {
                System.out.println("Thread A: Acquired lock A");

                try {
                    Thread.sleep(100);
                } catch (Exception ex) {}
                System.out.println("Thread A: Waiting for lock B");
                synchronized(lockObjectB) {
                    System.out.println("Thread A: Acquired lock on A and B");
                }
            }
        }
    }
    
    private static class MyThreadClassB extends Thread {
        public void run() {
            synchronized(lockObjectB) {
                System.out.println("Thread B: Acquired lock B");

                try {
                    Thread.sleep(100);
                } catch (Exception ex) {}
                System.out.println("Thread B: Waiting for lock A");

                synchronized(lockObjectA) {
                    System.out.println("Thread B: Acquired lock on A and B");
                }
            }
        }
    }
}

 

위의 코드 예제에서 교착 상태 문제를 해결하려면 아래 제공된 코드 스니펫에 표시된 것처럼 MyThreadClassB 클래스의 run 메서드에서 잠금 순서를 변경하기만 하면 된다.

public void run() {

    synchronized (lockObjectA) {
        System.out.println("Thread B: Acquired lock B");
        try { 
            Thread.sleep(100); 
        } catch (Exception ex) {}
        System.out.println("Thread B: Waiting for lock A");

        synchronized (lockObjectB) {
        	System.out.println("Thread B: Acquired lock on A and B");
        }
	}
    
}

 

아래는 전체코드이다.

public class MyThreadDeadlockDemo {

    public static Object lockObjectA = new Object();
    public static Object lockObjectB = new Object();

    public static void main(String args[]) {
        MyThreadClassA threadObjectA = new MyThreadClassA();
        MyThreadClassB threadObjectB = new MyThreadClassB();

        threadObjectA.start();
        threadObjectB.start();
    }

    private static class MyThreadClassA extends Thread {
        public void run() {
            synchronized(lockObjectA) {
                System.out.println("Thread A: Acquired lock A");

                try {
                    Thread.sleep(100);
                } catch (Exception ex) {}
                System.out.println("Thread A: Waiting for lock B");

                synchronized(lockObjectB) {
                    System.out.println("Thread A: Acquired lock on A and B");
                }
            }
        } 
    }

    private static class MyThreadClassB extends Thread {
  
        public void run() {
            synchronized(lockObjectA) {
                System.out.println("Thread B: Acquired lock B");

                try {
                    Thread.sleep(100);
                } catch (Exception ex) {}
                System.out.println("Thread B: Waiting for lock A");

                synchronized(lockObjectB) {
                    System.out.println("Thread B: Acquired lock on A and B");
                }
            }
        }
    }
}

 

참고 : https://www.developer.com/java/java-prevent-thread-deadlock/

반응형