본문 바로가기

3.구현/Java or Kotlin

Java thread에서 IllegalMonitorStateException 예외 발생문제

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