티스토리 뷰

멀티 스레드간에 동기화를 하기위해선 락을 잡아야한다. 자바에서 락을 잡는법은 모두들 알고있을 synchronized 키워드를 이용한 모니터락을 잡는방법과 1.5부터 추가된 Lock 인터페이스의 구현체를 이용하는 방법이 있다. 이 두 방식에서 각각 데드락 상태가 됐을때 스레드덤프의 내용은 동일한 내용일까? 이 포스팅에서는 그 차이를 확인해보려한다.

 

먼저 synchronized 키워드를 이용한 간단한 데드락 코드를 작성하자.

private final Object lock1 = new Object();                          
private final Object lock2 = new Object();                          
                                                                    
public static void main(String[] arg) {                             
    ExecutorService es = Executors.newFixedThreadPool(2);           
                                                                    
    First f = new First();                                          
    es.execute(() -> {                                              
        for(int i = 0; i < 99999; i++) {                            
            f.method1();                                            
        }                                                           
    });                                                             
                                                                    
    es.execute(() -> {                                              
        for(int i = 0; i < 99999; i++) {                            
            f.method2();                                            
        }                                                           
    });                                                             
                                                                    
    es.shutdown();                                                  
}                                                                   
                                                                    
public void method1() {                                             
    synchronized (lock1) {                                          
        synchronized (lock2) {                                      
                                                                    
        }                                                           
    }                                                               
}                                                                   
                                                                    
public void method2() {                                             
    synchronized (lock2) {                                          
        synchronized (lock1) {                                      
                                                                    
        }                                                           
    }                                                               
}                                                                   

두 스레드에서 각각 2개의 락을 공통되지않은 순서로 잡으려하고 그 과정에서 데드락이 발생하게된다.

 

이번엔 Lock 인터페이스를 이용한 코드를 작성하자.

private final Lock lock1 = new ReentrantLock();             
private final Lock lock2 = new ReentrantLock();             
                                                            
public static void main(String[] arg) {                     
    ExecutorService es = Executors.newFixedThreadPool(2);   
                                                            
    First f = new First();                                  
    es.execute(() -> {                                      
        for(int i = 0; i < 99999; i++) {                    
            f.method1();                                    
        }                                                   
    });                                                     
                                                            
    es.execute(() -> {                                      
        for(int i = 0; i < 99999; i++) {                    
            f.method2();                                    
        }                                                   
    });                                                     
                                                            
    es.shutdown();                                          
}                                                           
                                                            
public void method1() {                                     
    try {                                                   
        lock1.lock();                                       
        lock2.lock();                                       
    } finally {                                             
        lock2.unlock();                                     
        lock1.unlock();                                     
    }                                                       
}                                                           
                                                            
public void method2() {                                     
    try {                                                   
        lock2.lock();                                       
        lock1.lock();                                       
    } finally {                                             
        lock1.unlock();                                     
        lock2.unlock();                                     
    }                                                       
}                                                           

내용자체는 synchronized 를 이용할때와 동일하며 방식에따른 코드만 다르다. 두 코드는 모두 데드락이 발생하게된다.

 

이제 두 코드를 각각 실행시키고 데드락 상태에서 스레드 덤프를 생성하자. 스레드 덤프를 생성하는 방법은 여러가지가 있으니 각자 아는 방식으로 생성하면 된다. 나는 개인적으로 가장 선호하는 jcmd 방식을 이용한다.

jcmd {pid} Thread.dump > {file_name}

생성된 2개의 스레드덤프를 비교해보자. 위 예제코드를 그대로 사용했다면 데드락에 걸린 두개의 스레드는 각각 으로  pool-1-thread-1, pool-1-thread-2 명명돼있을것이다. 그리고 이 두개의 스레드에 대한 덤프 내용이 서로 다른것도 확인할 수 있다.

"pool-1-thread-1" #20 prio=5 os_prio=31 cpu=1.44ms elapsed=24.89s tid=0x00007ff33c02e800 nid=0xa103 waiting for monitor entry  [0x000070000cd20000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.yong.first.First.method1(First.java:40)
	- waiting to lock <0x000000061fe551e0> (a java.lang.Object)
	- locked <0x000000061fe551d0> (a java.lang.Object)
    
"pool-1-thread-2" #21 prio=5 os_prio=31 cpu=0.85ms elapsed=24.89s tid=0x00007ff33c058000 nid=0x6703 waiting for monitor entry  [0x000070000ce23000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.yong.first.First.method2(First.java:50)
	- waiting to lock <0x000000061fe551d0> (a java.lang.Object)
	- locked <0x000000061fe551e0> (a java.lang.Object)

synchronized 를 이용한 덤프의 내용이다. 1 스레드가 BLOCKED 상태이며 551e0 락을 잡기위해 대기중임을 알 수 있다. 551d0 락은 이미 획득한 상태다. 2 스레드는 반대로 551e0 락을 획득한 상태이며 551d0 락을 대기하고있다. 데드락이다.

"pool-1-thread-1" #20 prio=5 os_prio=31 cpu=1.14ms elapsed=138.63s tid=0x00007fbe99007800 nid=0xa203 waiting on condition  [0x0000700009258000]
   java.lang.Thread.State: WAITING (parking)
   
"pool-1-thread-2" #21 prio=5 os_prio=31 cpu=0.67ms elapsed=138.63s tid=0x00007fbe9a99c000 nid=0xa003 waiting on condition  [0x000070000935b000]
   java.lang.Thread.State: WAITING (parking)

Lock 을 이용한 덤프의 내용이다. 스레드의 상태도 WAITING 으로 되어있고 덤프 내용도 다르다. 두 개를 비교했을때 좀 더 명확하게 데드락임을 알 수 있는 내용은 synchronized 를 사용했을때다.

 

다만 덤프의 스크롤을 좀 더 내려보면 두 덤프에서 모두 Found one Java-level deadlock: 란 항목을 볼 수 있다.

"pool-1-thread-1":
  waiting to lock monitor 0x00007ff330d55f00 (object 0x000000061fe551e0, a java.lang.Object),
  which is held by "pool-1-thread-2"
"pool-1-thread-2":
  waiting to lock monitor 0x00007ff330d55e00 (object 0x000000061fe551d0, a java.lang.Object),
  which is held by "pool-1-thread-1"
"pool-1-thread-1":
  waiting for ownable synchronizer 0x000000061fe55558, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
  which is held by "pool-1-thread-2"
"pool-1-thread-2":
  waiting for ownable synchronizer 0x000000061fe55528, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
  which is held by "pool-1-thread-1"

내용이 살짝 다른부분이 존재하지만 이 항목에선 꽤나 비슷한 내용으로 데드락을 찾아준다.

 

이번 포스팅의 목적은 뭐가 더 나은지를 알아본다기보다는 암묵적인 모니터락(synchronized)과 명시적인 Lock 객체를 이용할때 자바는 동일한 메커니즘으로 동작하는지가 궁금했고, 결과적으론 자세힌 모르겠지만 다르게 동작한다는걸 알게됐다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함