티스토리 뷰
멀티 스레드간에 동기화를 하기위해선 락을 잡아야한다. 자바에서 락을 잡는법은 모두들 알고있을 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 객체를 이용할때 자바는 동일한 메커니즘으로 동작하는지가 궁금했고, 결과적으론 자세힌 모르겠지만 다르게 동작한다는걸 알게됐다.
'Java' 카테고리의 다른 글
ThreadPoolExecutor 사용시 maximumPoolSize 동작방식 (5) | 2021.02.02 |
---|---|
UncaughtExceptionHandler 인터페이스 (0) | 2021.01.25 |
동기화 클래스 사용하기(CountDownLatch, Semaphore, CyclicBarrier) (0) | 2020.03.05 |
JDK 14 Release note (0) | 2020.02.07 |
DesignPattern#04. Singleton Pattern (0) | 2019.10.19 |
- Total
- Today
- Yesterday
- Kotlin
- Jackson
- toby
- mariadb
- db
- OOP
- go-core
- Git
- JavaScript Core
- Design Pattern
- frontcode
- DesignPattern
- spring cloud
- 정규표현식
- code
- programming
- frontend개발환경
- backend개발환경
- java8
- http
- TEST
- java
- MySQL
- Spring
- servlet
- JPA
- javascript
- generics
- clean code
- EffectiveJava
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |