월요일, 8월 01, 2016

자바 스레드 기본구조 및 구현 방법


자바는 사용자가 직접 스레드를 생성하고 관리하여 멀티태스킹 환경을 구축할 수 있도록 도와준다. 시간이 오래걸리는 작업을 백그라운드에서 수행하거나, 타이머 (Timer), 소켓 프로그래밍 (Socket Programming) 등에 널리 활용될 수 있다.

이 스레드를 자바 어플리케이션에서 어떻게 직접 구현해야 하는지 기본구조를 소스코드와 함께 개괄적으로 살펴보도록 하겠다.

우선, 스레드를 구현하는 방법은 여러가지가 있는데, 대표적으로 두 가지 방법이 가장 많이 사용된다.
1. Thread라는 클래스를 상속 (Extending Thread class)
2. Runnable이라는 인터페이스를 구현 (Implementing Runnable interface)
이 두 가지 방법을 사용해 새로운 클래스를 정의하고 그 안에 수행하고자 하는 내용을 채워넣으면 되는 것이다.

첫 번째 방법과 두 번째 방법 모두 내부적으로 run()이라는 추상 메서드 (Abstract Method)가 있는데, 이 메서드에 내용을 채워넣으면 사용자 정의 스레드가 완성된다.
예를 들어, 10까지 카운트다운을 해주는 타이머를 간단하게 만들어보면 다음과 같이 코드를 작성하면 된다.
그리고 이렇게 새로 정의한 사용자 정의 스레드를 외부에서 생성하여 사용할 때는 다음과 같이 간단하게 인스턴스화시키고, start()라는 메서드를 호출하면 된다.
run()이 아니라 start()를 호출함에 주의해야 한다. 만약 th.run()을 호출할 경우, 그냥 th라는 클래스에 정의된 run()이라는 메서드를 한 번 호출할 뿐, 실제로 스레드가 생성되어 실행되는 것은 아니다. start() 메서드를 호출해야 정상적으로 스레드를 위한 Call stack이 생성되고 run() 메서드가 자동으로 실행된다.

이렇게 Thread라는 클래스 자체를 상속하는 방법은 간단한 스레드를 만들어서 테스트용으로 활용하기에는 좋지만, 좀 더 세부적이고 다양한 기능을 가진 사용자 정의 스레드를 구현하기에는 한계가 있다.

따라서 두 번째 방법인 Runnable 인터페이스를 구현하는 것을 추천한다. 이 방법을 사용하면 단순히 스레드라는 개념이 아니라 '객체 (Object)'라는 개념을 십분활용하여 클래스를 정의하기 때문에 매우 활용성이 높다. 게다가 가장 큰 장점은, 바로 인터페이스를 사용한다는 점이다. 자바의 특성상 상속받을 수 있는 부모 클래스는 한 개로 제한되어 있다. 그런데, 인터페이스를 구현하는 것은 사실상 무제한이기 때문에 상속의 문제에 있어서 자유롭다.

이 방법을 통해 좀 더 스레드를 입맛에 맞게 튜닝(?)하고, 생성, 종료시키는 방법을 알아보도록 하겠다. 이 과정에서 기초적인 뼈대코드를 만들어볼텐데, 스레드를 활용하고 싶을 때마다 언제든지 가져다가 쓰면 편리할 것이다.

우선 Runnable 인터페이스는 앞서 살펴본 Thread 클래스와 마찬가지로 run() 메서드를 가지고 있는 인터페이스이다. 이름그대로 run() 메서드를 사용하기 위한 인터페이스이다.

Thread라는 클래스 내부에 run()이라는 메서드를 직접적으로 정의하지 않고 new Thread(Runnable r) 이런식으로 Runnable 인터페이스를 구현한 클래스를 인자로 전달하여, Thread 클래스가 자신의 것이 아닌 인자로 전달받은 클래스의 run() 메서드를 수행할 수 있도록 만들 수 있다.

즉, Thread 클래스는 비디오 플레이어이고 Runnable 인터페이스는 비디오라고 이해하면 편하다. 실제 비디오에 내용이 담겨있고, Thread 클래스는 이 비디오를 받아서 그 비디오의 내용을 재생해주는 것이다.

그렇다면, 뼈대 코드를 살펴보자.
이렇게 내부적으로 Thread th라는 변수를 정의해두고 사용하면 편리하다. 클래스의 생성자에서 MyThread가 인스턴스화될 때 미리 정의해둔 th를 역시 인스턴스화해주는데, 이때 this, 즉, Runnable 인터페이스를 구현한 MyThread 클래스 자체를 인자로 넘겨준다. 이렇게 하면 내부적으로 정의된 th가 내부적으로 정의된 run() 메서드를 실행시키는 스레드가 된다.

그리고 외부에서 MyThread를 생성하고 실행하고자 start() 메서드를 호출했을 때 실제 스레드인 th가 실행되도록 start() 메서드를 사용자 정의해준다. 물론, 이 메서드 이름은 아무렇게나 바꿔도 상관이 없다.

그리고 중요한 것은 실행중인 스레드를 중단하는 것인데, 원래 stop()이라는 메서드가 Thread 클래스에 정의되어서 스레드를 종료시킬 때 사용되었었다. 그러나 현재 deprecated되어서 더이상 사용되지 않는다.

run() 메서드에 정의된 내용이 단발적인 코드라면 스레드가 해당 내용을 수행하고 자동으로 종료되기 때문에 문제가 없지만, 무한루프를 도는 스레드라면 종료 시점을 사용자가 명시해주어야 하는 경우가 발생하게 된다.

이러한 경우 stop() 메서드 대신 대표적으로 interrupt()라는 메서드를 사용한다. 사실 이것은 종료의 개념과는 다소 상이하지만, 다음과 같이 코드를 작성하면 루프를 빠져나오는데 사용될 수 있다.
그리고 이렇게 사용자 정의된 클래스를 외부에서 생성/실행/종료할 때는 다음과 같이 직관적으로 사용하면 된다.
이외에도 스레드의 생성주기와 관련한 실행제어에 있어서 join(), resume(), sleep(), suspend(), yield() 등의 다양한 메서드들이 있는데, 자세한 사항은 아래 링크의 문서에 나와있다.
Source Code
public class MyThread implements Runnable{
private Thread th;
public MyThread() {
th = new Thread(this);
}
public void run() {
while(!Thread.currentThread().isInterrupted()) {
}
}
public void start() {
th.start();
}
public void stop() {
th.interrupt();
}
}

댓글 없음:

댓글 쓰기