목차0. 개요
1. 일관적인 LOCK 순서를 이용한 데드락 해소
0. 개요
a. 데드락이 먼데"니가 먼저 놔" 상황이다.
여러 쓰레드를 사용하는 경우 하나의 자원에 동시에 접근하게 하면 여러 문제들이 발생할 수 있는데 이를 해결하는 방법 중엔 "누가 자원 쓰고 있으면 해당 자원에 접근 불가" 토록하는 'LOCK'이란 개념이 있다. 근데 어쩌다 보니까 작업을 완료하는데 서로의 자원이 같은 시점에 필요한 경우가 발생하면 "상대가 먼저 놓기" 전까진 둘 다 작업을 진행 못하는 '교착 상태'가 발생한다.a. 데드락이 먼데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 사용 순서를 일관되게 바꾸면, 문제를 해결할 수 있다.
오늘 하루도 공부할 수 있어 크게 감사합니다
2024-01-21 개발자 최찬혁