쓰레드(Thread) 관련 메서드가 죄다 Deprecated 된이유.
쓰레드를 잠시 멈추거나 재개하고 싶을때, 정말 쉽사리 많이쓰던 메서드는 stop(), suspend(), resume()이다. 다만 이 메서드들은 현재 deprecated되어서 사용이 권장되지 않는다.
쓰레드를 이용한 복잡한 동시성 프로그램을 파악가능하고 완벽하게 만들기에는 거의 불가능에 가깝다는게 여러사람의 말이다.
스칼라에서도 마찬가지 개념이 전반적으로 깔려있지만, 쓰레드의 메서드가 deprecated된 이유는 자명하다. 안전하지 않기 때문이다. 그 전에 쓰레드의 개념에 대해 알 필요가 있다.
쓰레드는 쉬운개념이다. 우리가 동시에 두개의 일을 프로그램에게 시키고 싶을때 쓰레드를 사용한다.웹 프로그램 같은 경우 동시에 여러 사용자가 서버에 접속해서 일을 하게 되는데, 이를 효과적으로 동시에 처리를 해주어야 한다. 한 사용자가 서버를 온통 다잡고 있으면 웹 세상은 교통체증이 엄청 심할것이다.
다만 쓰레드는 동시에 무언가 작업을 하는것이 주 목적이지만 동시에 작업을 하면서도 쓰레드끼리 자원에 대한 공유가 일어난다. 예를들어 공장라인에 작업자가 셀방식으로 일하고있고 여러가지 자원은 중간의 바구니에 담겨있다고 하자. 이 바구니에서 노동자들은 제품 몇가지를 가져다가 그걸 수리하고 다시 바구니에 가져다 놓는다. 이 과정에서 다른 노동자가 점유하고 있는 물건은 다른 노동자가 점유해서는 안된다. 다른 노동자가 제품을 이것저것 만지고 있는데 다른사람이 와서 동시에 고치려고 시도한다면 [아 좀 기다려봐] 라고 말해야 한다. 일이 다 끝나면 그제서야 [야 일 끝났다] 라는 시그널과 함께 기다리고 있는 노동자에게 순차적으로 물건을 전달해줘야 할것이다.
Thread.stop()
다만 여기서 Thread.stop()이 등장한다. 이는 강제로 물건을 고치고 있는 노동자를 조용히 없애버리는(?) 하는 행위이다. 고치고 있는 제품은 고치다 만 찝찝한 상태로 (Damaged Object) 바구니에 담겨있게 된다. 뭐 아무렴 좋다. 물건의 상태에 따라 아무일도 없는것처럼 짤리지 않은 노동자가 가져다 고칠수도 있는것이고 원할하게 진행될수도 있다. (사실 대부분은 이럴것이다.) 그렇지만, 특정한 경우에는 이 결함이 있는 고치다 만 제품이 계속 공정을 지나가면서 더 큰문제를 야기시킬수가 있다. 그건 사용자도 모르고 아무도 에러가 터질때까지는 모른다. 만약 제품에 벌레가 들어간 상태였는데 이를 처리하던 과정에서 Thread.stop()이 등장한 경우 벌레가 들어간 제품은 아무도 모르게 숨어있다가 최종소비자가 쓰게될때 벌레가 튀어나올수 있다. 일이 엄청 커졌다.
Thread.stop() 굉장히 급작스럽게 Thread를 stop시키기 때문에 중간에 일이끊긴 쓰레드가 어느정도 작업을 하고있었는지 마무리할 시간이 없다. 물론 ThreadDeath Exception 이 내부적으로 조용히 쌓이는데, 사용자도 모르게 조용히 쌓이는 이유는 다음과 같다. 이 예외를 잡아다가 [야 얘 중간에 일이 끝났어!!] 라고 외치게 할수도 있겠다. 그렇지만 그렇게 못하는 이유는 다음과 같다.
첫번째는 - 프로그램 전체에서 이 에러가 던져질수 있다. 사실 공장 여러군데서 [야 얘 갑자기 짤렸어!!] 라고 여기저기 외치면 오히려 시끄럽기만하고 누가 끝났는지 알수도 없는 상태이다. 이를 처리하기 위해 노동자 음성인식을 기계학습으로 구현할것인가? 개소리다.
두번째는 - 이 에러를 처리하는데 있어 또 다른 ThreadDeath 가 연쇄적으로 터질수 있다. 프로그램적으로 그렇다.
그야말로 벌레를 잡으려고 초가삼간을 다 태우는 꼴이 될수 있다는 것이다. 그렇지만 벌레를 잡지 않아도 그건 그것대로 큰문제가 된다. 게다가 이 벌레는 여기저기 말라리아를 전염시키고 다니는 골치아픈 녀석일수도 있다.
여기서 하나 예제를 풀어볼수 있다. 다음과 같이 Runnable 인터페이스에 run()메서드를 구현한다고 치자.
public final class Container implements Runnable {
static int j = 0;
static int i = 100;
public synchronized void run() {
while (i > 0) {
j++;
i--;
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Container());
thread.start();
Thread.sleep(5000);
thread.stop();
}//end main
}
while문안에 두가지 작업이 일어나고 있다. j와 i는 항상 짝이 맞아서 합이 100이여야 한다고 치자. 하지만 중간에 Thread.stop() 메서드가 호출되어 j++만 실행하고 끝났다고 치자. 하지만 이렇게 끝나더라도 사용자 입장에서는 어떻게 끝났는지 알수가 없다. while문 안의 구문은 통으로 다뤄져야 하며 임계구역(Critical Section)이다.
이는 while문을 돌리기 위한 조건 i>0 외 flag == true 이라는 문장을 추가하여 통제를 할수가 있다. 즉, Thread.stop()으로 쓰레드를 멈추는 대신 외부에서 flag를 변경해서 아예 임계구역안으로 실행이 되지 않도록 프로그램을 구성할수 있다는 것이다. 다만, 임의의 플래그를 쓰는 것보다 더 나은 방법이 있다. thread 자체적으로 제공하는 interrupt() 메서드를 이용하면 된다. 이는 갑자기 노동자가 일을 하다 사라져버리는 상황이 아닌, 노동자에게 일하는 도중 너 그만두어야 되겠다는 말을 하는것과 같고, 노동자는 자신의 상황에 맞추어 interrupt() 시그널을 받은 동시에 그만둘수도 있고 그렇지 않으면 일을 마무리하고 나갈수도 있다. (물론 프로그램의 노동자는 말을 잘듣는 노동자여서 이유없는 나쁜짓은 하지 않는다.)
따라서 코드는 다음과 같이 바꿔볼수 있다.
public final class Container implements Runnable {
static int j = 0;
static int i = 100;
public synchronized void run() {
while (i > 0 && !Thread.interrupted()) {
j++;
i--;
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Container());
thread.start();
Thread.sleep(5000);
thread.interrupt();
}//end main
}
달라진것은 interrupt가 되었는지 체크하는 여부와, stop()대신 interrupt()가 호출된 것이다.
다만 한가지 유의해야 할 점은 interrupted()메서드가 호출되는 동시에 쓰레드가 interrupted 받았냐의 프로그램 상태는 참에서 거짓으로 바뀐다는 것이다. 만약 이를 무시하고 계속 참으로 유지하고 싶다면 isInterrupted()메서드를 써야한다.
Thread.suspend() / Thread.resume()
이 두 메서드도 비슷한 이유에서 deprecated되었다. suspend()는 잠시 대기하는 목적인데, 이러한 경우 공유된 자원에 대한 다른쓰레드들의 접근이 불가능하다. 만약에 쓰레드끼리 서로 공유된 자원에 대해 접근하기 위해 resume되어있지 않은 놈과 서로 엉키다 보면 데드락이 발생하기 쉽다.
데드락은 아래와 같은 상황.. 서로에 대한 자원을 차지하려다가 양보도 못하고 요청만 하고있는 상태
Thread.destroy()
껍데기만 있는 신기한 메서드인데 자바개발자 내부에서도 어느정도 논란이 있었다고 인정하고 있다. 목적은 아무런 마무리작업도 하지 않은채 노동자를 파괴(?) 해버리는 메서드였는데, 다른 메서드들보다 과격하다고 느꼈는지 구현도 해놓지않았다.
공유자원에 대한 락이 걸린 상태로 쓰레드가 파괴되어버리면 프로그램이 교착상태로 빠져버릴 위험성이 있기 때문이다. 하지만 나중에 구현할 생각이 눈꼽만치는 있어서 그런지 deprecated는 표시해놓지도 않다가 결국에는 deprecated되었다. 현재 자바에서는 이 메서드를 호출하면 껍데기는 있으면서도 메서드가 구현이 되어있지 않기 때문에 NoSuchMethodError (그런 메서드 없음!!) 런타임 에러만 떨어진다.
여기서 하나 파악할수 있는게, 제대로 만들지 못한 메서드인 경우 아예 삭제해도 되는데 영향도 때문에 쉽사리 없애지 못한다는 것이다. 자바의 프로그래머들이 실무에서는 어떻게 쓰고 있는지 알수있는 방법이 없기 때문에 수많은 자바 사용자들을 위해 어쩔수 없이 남겨놓을 수 밖에 없다. 자바의 언어상 변화에 보수적인 스탠스를 취하는 이유는 바로 이러한 점에서 비롯된다.
참고자료.
http://docs.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html
http://geekexplains.blogspot.kr/2008/07/alternatives-of-stop-suspend-resume-of.html?m=1