노래를 재생하면서 워드 작업을 하고, 파일을 다운로드 받는 등 동시에 여러가지 작업을 수행할 수 있도록 하는 것을 말한다.
프로세스란 현재 실행되고 있는 프로그램을 말하는데, 자바 프로그램은 JVM 위에 실행되고, JVM도 프로그램 중 하나이다. 운영체제 입장으로 보면 자바도 하나의 프로세스를 실행하는 것이다.
그리고 워드 프로세서가 하나의 프로세스라면, 그 안에서도 여러개의 흐름이 동작할 수 있다. 이것을 Thread라고 하며, 자바 프로그램이 여러개의 작업을 동시에 하도록 만들고 싶다면 Thread를 공부해야 한다.
자바에서 Thread를 만드는 방법은 크게 Thread 클래스를 상속받는 방법과 Runnable인터페이스를 구현하는 방법이 있다.
Thread 클래스를 상속받아 쓰레드를 생성하는 방법을 먼저 알아보자면, 아래와 같이 java.lang 패키지의 Thread를 상속받고 Thread가 가지고 있는 run()메소드를 오버라이딩해야한다.
public class MyThread extends Thread{
@Override
public void run() {
super.run();
}
}
Thread가 가진 run() 메소드는 수행 흐름이 하나 더 생겼을 때의 메소드로, 메인 메소드와는 다른 흐름의 메인 메소드 정도로 이해하면 된다. run() 메소드에서는 뭐든, 원하는 코드를 작성하면 된다.
public class MyThread1 extends Thread {
String str;
public MyThread1(String str){
this.str = str;
}
public void run(){
for(int i = 0; i < 10; i ++){
System.out.print(str);
try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
본 포스팅의 예시에서는 str을 입력받고 10회 반복하며 출력하는 프로그램을 만들었다. 실행결과가 너무 빠른 것보다는 눈으로 확인할 수 있을 정도의 속도로 느린 것이 차라리 나아서 sleep() 메서드를 이용해 랜덤한 초 단위 시간동안 잠시 쉬었다가 출력할 수 있게 했다. 그리고 sleep 메소드가 발생시킬 수 있는 예외를 try-catch문으로 감싸주었다.
public class ThreadExam1 {
public static void main(String[] args) {
// MyThread인스턴스를 2개 만듭니다.
MyThread1 t1 = new MyThread1("*");
MyThread1 t2 = new MyThread1("-");
t1.start();
t2.start();
System.out.print("!!!!!");
}
}
그리고 위에서 만든 MyThread1을 사용하는 클래스 만들어 MyThread1에 대한 객체를 생성하고, 테스트를 하려고 한다. 그런데 쓰레드를 실행할 때에는 run() 메소드가 아니라 start() 메소드를 호출해야 한다. start() 메소드는 쓰레드가 시작할 준비를 하는 역할을 하며, 준비가 다 되었을 때 run() 메소드를 호출해주는 메소드이다. 즉 start() 메소드를 호출해주지 않으면 쓰레드는 동작하지 않는다.
start() 메소드를 호출하면 프로그램의 흐름이 2개로 나뉘게 된다. 메인을 수행하던 흐름과, 쓰레드 run()이 실행되는 흐름. 이런 상황에서 쓰레드를 하나 더 실행하면 흐름이 3개가 되는 것이다.
그리고, 만약 메인 쓰레드가 종료된다고 하더라도 프로그램이 종료되는 것은 아니다. 실행된 다른 모든 쓰레드가 종료될 때에만 프로그램이 종료된다.
자바에서 Runnable 인터페이스를 구현해 쓰레드를 만들 수 있는 방법을 지원하는 이유는 자바가 단일 상속만 가능하기 때문이다. 이미 다른 클래스를 상속받고 있는데 Thread 클래스를 또 상속받을 수는 없다. 하지만 인터페이스는 여러개를 구현해 사용할 수 있기때문에 다른 클래스를 상속받고 있다면 Runnable 인터페이스를 구현해서 쓰레드를 사용하면 된다.
public class MyThread2 implements Runnable {
@Override
public void run() {
}
}
Runnable 인터페이스를 구현해서 쓰레드를 만드는 방법은 마찬가지로 Runnable 인터페이스가 가지고 있는 run()메소드를 오버라이딩하는 것이다.
public class MyThread2 implements Runnable {
String str;
public MyThread2(String str){
this.str = str;
}
public void run(){
for(int i = 0; i < 10; i ++){
System.out.print(str);
try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thread 클래스 상속받아 쓰레드를 만든 MyThread1 클래스 예시처럼 String 필드를 하나 가지고 있게 하고, 처음 객체가 생성 될 때 부터 String 값을 가지고 생성할 수 있도록 생성자 하나를 만든다. 그리고 run() 메소드에서도 MyThread1 클래스와 동일하게 열번 돌면서 str 값을 출력하는 코드를 구현한다.
public class ThreadExam2 {
public static void main(String[] args) {
MyThread2 r1 = new MyThread2("*");
MyThread2 r2 = new MyThread2("-");
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
System.out.print("!!!!!");
}
}
그리고 ThreadExam2 클래스를 만들어 Runnable 인터페이스 구현으로 만든 쓰레드를 사용해보도록 한다. MyThread1은 Thread 클래스를 상속 받았지만 MyThread2는 Runnable 인터페이스를 구현했고, Thread 클래스를 상속 받지 않았기 때문에 Thread가 아니다. 그래서 start() 메소드가 없다.
이런 문제를 해결하기 위해 진짜 쓰레드 객체를 생성해야 한다. Thread의 생성자를 보면 Runnable 을 받아들일 수 있도록 되어있다. MyThread2는 Runnable 인터페이스를 구현하고 있기때문에 이 생성자를 이용할 수 있다. 그렇게 Thread 생성자에 Runnable 인터페이스 객체로 만든 쓰레드를 넣어주면 된다. 그러면 이제는 Thread 객체가 strat() 메소드를 가질 수 있기 때문에 start() 메소드를 호출하면 된다.
상속받는 것보다는 사용 방법이 불편하게 느껴질 수도 있지만 Thread 클래스를 바로 상속 받는 것이 어려운 경우 사용하면 유용하고, 실행 결과도 동일하다.