Java thread에서 IllegalMonitorStateException 예외 발생문제
작성자: ospace114 @ naver.컴(07.11.28)
자바는 개발자에게 상당히 편한 환경을 제공한다. 왠만한 기능들이 쉽게사용하도록 제공하기 때문이다. 그리고 상당히 많은 제약을 제공한다.
그중에서 쓰레드 사용에 있어서 제약사항중에 IllegalMonitorStateException 예외를 보도록 하겠다.
예외가 발생할 수 있는 상황은 매우많다. 그렇기에 특수한 상황일 경우에 대해서 살펴보겠다.
이번 예제는 ThreadA와 ThreadB가 있고 ThreadB가 ThreadA에서 메시지 처리 요청을 보내는 상황이다. 이를 코드로 구현하면 다음과 같다.
<ThreadA.java 소스코드>
import java.util.Vector;
public class ThreadA implements Runnable {
Vector recvMessages;
public ThreadA() {
recvMessages = new Vector();
}
public synchronized void run() {
while(true) {
if( recvMessages.isEmpty()) {
try {
recvMessages.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("Receive Message: " + (String)recvMessages.remove(0));
}
}
public void processMessage(String message) {
this.recvMessages.add(message);
this.recvMessages.notify();
}
}
<ThreadB.java 소스코드>
public class ThreadB implements Runnable {
ThreadA threadA;
public ThreadB(ThreadA threadA) {
this.threadA = threadA;
}
public synchronized void run() {
for(int i=0; i<5; i++) {
threadA.processMessage("Message" + i);
}
}
}
<ThreadMain.java 소스코드>
public class ThreadMain {
public static void main(String[] args) {
ThreadA runA = new ThreadA();
ThreadB runB = new ThreadB(runA);
new Thread(runA).start();
new Thread(runB).start();
}
}
위의 소스 코드를 보면서 약간 의아해 할 수 있다. 두 쓰레드간의 동기화를 Vector의 wait()와 notify()를 사용하고 있기 때문이다. 일반적인 쓰레드 동기화처럼 하고 싶다면 ThreadA의 run() 부분을 다음과 같이 하면 될 것이다.
public synchronized void run() {
...
if(rcvMessage.isEmpty()) {
...
wait();
...
}
...
}
이렇게 하면 다음에 오는 processMessage()에 notify()도 바뀌어야 한다. 혹은 ThreadB의 run()에서 ThreadA로 notify()를 보내도 되지만 작업이 번거롭기 때문에 processMessage()에 notify()만 변경해주는게 좋다.
아무튼 이런 방식으로 쓰레드를 동기화할 수 있구나 정도만 알고 실제 실행을 해보자.
이렇게 해서 실행하면 다음과 같은 결과를 기대한다.
그러나 위와 같은 결과는 보지 못하고 다음과 같은 결과를 볼 것이다.
상당히 난감하다. 일부는 Vector 객체로 동기화했으니 일어나는 문제이기 때문에 Vector에 대해서 대기와 통지를 제거해야된다고 생각할 수 있다. 원인은 맞지만 해결책은 틀렸다. Vecotr를 가지고도 동기화를 제공할 수 있다.
그러면 무엇이 문제인가? 자세히 보변 Vector객체인 recvMessages가 두 쓰레드에 의해서 사용되고 있다. 즉, 두 쓰레드가 recvMessages객체를 동시에 사용하면 안 된다는 의미이다.
그렇다면 recvMessage객체를 사용하는 run()과 processMessage() 메소드에 대해서 synchronized를 해주면 되지 않을까 생각한다.
public synchronized void run() {
...
}
public synchrnoized void processMessage(String message) {
...
}
그러나 메소드에 대한 synchronized는 해당 메소드 내에서만 유효하지 다른 메소드까지 영향을 미치지 않은다는 문제점이 있다. 그러면 어떻게 할까?
바로 recvMessages객체에 해서 synchronized를 해주면 된다.
synchronized(recvMessages) {
....
}
그러면 run()은 다음과 같이 변경된다. run() 실행되면 항상 내부에서는 recvMessages객체와 동기화가 일어난다.
public synchronized void run() {
while(true) {
synchronized(recvMessages) {
if( recvMessages.isEmpty()) {
...
recvMessages.wait();
...
}
...
}
}
}
이부분은 processMessage()에서도 마찬가지이다. 주의할 것은 processMessage() 메소드에 있는 synchronized를 제거해줘야한다. 그렇지 않으면 데드락이 발생한다.
다음에 수정된 ThreadA.java 소스코드이다.
<수정된 ThreadA.java 소스코드>
import java.util.Vector;
public class ThreadA implements Runnable {
Vector recvMessages;
public ThreadA() {
recvMessages = new Vector();
}
public synchronized void run() {
while(true) {
synchronized(recvMessages) {
if( recvMessages.isEmpty()) {
try {
recvMessages.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("Receive Message: " + (String)recvMessages.remove(0));
}
}
}
public void processMessage(String message) {
synchronized(recvMessages) {
this.recvMessages.add(message);
this.recvMessages.notify();
}
}
}
'3.구현 > Java or Kotlin' 카테고리의 다른 글
Java 쓰레드 상태 (0) | 2008.07.24 |
---|---|
Jar 패키징(Packaging) (0) | 2008.07.21 |
Reactor 패턴의 예제 코드 (0) | 2008.07.17 |
SLF4J simple tutorial (0) | 2007.12.20 |
MS-Sql 서버에서 iBatis 사용 강좌 (0) | 2007.10.26 |