본문 바로가기
테스트코드

[ThreadSafety] 데드락 테스트

by 무자비한 낭만주먹 2024. 1. 21.

[그림1]. 오늘도 감사한 공부 시작 ~

목차
0. 개요
1. 일관적인 LOCK 순서를 이용한 데드락 해소

 

0. 개요
a. 데드락이 먼데
[그림2]. 숨막히는 교착상태
"니가 먼저 놔" 상황이다.


여러 쓰레드를 사용하는 경우 하나의 자원에 동시에 접근하게 하면 여러 문제들이 발생할 수 있는데 이를 해결하는 방법 중엔  "누가 자원 쓰고 있으면 해당 자원에 접근 불가" 토록하는 'LOCK'이란 개념이 있다. 근데 어쩌다 보니까 작업을 완료하는데 서로의 자원이 같은 시점에 필요한 경우가 발생하면 "상대가 먼저 놓기" 전까진 둘 다 작업을 진행 못하는 '교착 상태'가 발생한다.

a. 데드락이 먼데
[그림2]. 일관적이지 않은 LOCK 순서가 일으키는 데드락 상황
LOCK을 하는 순서가 일관적이지 않을 때


위 경우처럼 락 순서가 일관적이지 않고 어떤 스레드에선 '1, 2' 순서로 .. 어떤 스레드에선 '2, 1' 순서로 락을 발생시킬 때 교착상태가 발생할 수 있다. 일단 테스트 코드를 통해 재현해보자.

 

1. 일관적인 LOCK 순서를 이용한 데드락 해소
a. 데드락을 발생시켜보자
public class DeadLockTest {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public DeadLockTest() throws ExecutionException, InterruptedException {
        this.test1();
    }

    @DisplayName("DeadLockTest (1): 데드락 발생 테스트")
    @Test
    protected void test1(){
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Future<?> future1 = executorService.submit(new ThreadDemoFirstBadCase());
        Future<?> future2 = executorService.submit(new ThreadDemoSecondBadCase());

        Assertions.assertThrows(TimeoutException.class, () -> {
            future1.get(15, TimeUnit.SECONDS);
            future2.get(15, TimeUnit.SECONDS);
        });
    }

    private static class ThreadDemoFirstBadCase extends Thread {
        public void run() {
            synchronized(lock1){
                System.out.println("Thread 1: Holding lock 1...");

                try { Thread.sleep(10000); }
                catch (InterruptedException e) {}
                System.out.println("Thread 1: Waiting for lock 1..");

                synchronized (lock2) {
                    System.out.println("Thread 1: Holding lock 1 & 2...");
                }
            }
        }
    }

    private static class ThreadDemoSecondBadCase extends Thread {
        public void run() {
            synchronized (lock2) {
                System.out.println("Thread 2: Holding lock 2...");

                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {}
                System.out.println("Thread 2: Waiting for lock 2...");

                synchronized (lock1) {
                    System.out.println("Thread 2: Holding lock 1 & 2...");
                }
            }
        }
    }

}
교착 상태가 발생해 TimeOut이 발생한다.

 

b. 해결
public class DeadLockTest {
    private static final Object lock3 = new Object();
    private static final Object lock4 = new Object();

    public DeadLockTest() throws ExecutionException, InterruptedException {
        this.test2();
    }
    
    @DisplayName("DeadLockTest (2): 데드락 발생 해결 테스트")
    @Test
    protected void test2() throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Future<?> future3 = executorService.submit(new ThreadDemoFirstGoodCase());
        Future<?> future4 = executorService.submit(new ThreadDemoSecondGoodCase());

        future3.get();
        future4.get();
    }

    private static class ThreadDemoFirstGoodCase extends Thread {
        public void run() {
            synchronized(lock3){
                System.out.println("Thread 1: Holding lock 1...");

                try { Thread.sleep(10000); }
                catch (InterruptedException e) {}
                System.out.println("Thread 1: Waiting for lock 1..");

                synchronized (lock4) {
                    System.out.println("Thread 1: Holding lock 1 & 2...");
                }
            }
        }
    }

    private static class ThreadDemoSecondGoodCase extends Thread {
        public void run() {
            synchronized (lock3) {
                System.out.println("Thread 3: Holding lock 1...");

                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {}
                System.out.println("Thread 3: Waiting for lock 1...");

                synchronized (lock4) {
                    System.out.println("Thread 3: Holding lock 1 & 2...");
                }
            }
        }
    }
}
두 쓰레드의 LOCK 사용 순서를 일관되게 바꾸면, 문제를 해결할 수 있다.

 

[그림3]. 해결 방법

 

 

오늘 하루도 공부할 수 있어 크게 감사합니다

2024-01-21 개발자 최찬혁