이 글은 원 저자 Jakob Jenkov의 허가로 포스팅된 번역물이다.

원문 URL : http://tutorials.jenkov.com/java-concurrency/creating-and-starting-threads.html



자바 쓰레드는 자바의 다른 오브젝트들과 다르지 않다. 쓰레드는 java.lang.Thread 클래스의 인스턴스이거나, 이 클래스의 서브클래스의 인스턴스이다. 그리고 쓰레드는 오브젝트이면서 코드를 실행하는 일도 가능하다.


 

자바 쓰레드의 생성과 시작


자바에서 쓰레드의 생성을 다음과 같은 코드로 이루어진다.


  Thread thread = new Thread();


자바 쓰레드를 실행하기 위해 start() 메소드를 호출한다.


  thread.start();


이 예제에 쓰레드의 실행을 위한 구체적인 코드는 없다. 쓰레드는 실행된 다음 곧바로 종료될 것이다.


쓰레드를 실행하는 코드에는 두 가지 방법이 있다. 하나는 Thread의 서브클래스를 만들어 run() 메소드를 오버라이딩 하는 것이고, 다른 하나는 java.lang.Runnable 인터페이스를 구현한 오브젝트를 Thead의 생성자의 파라미터로 넘기는 방법이다. 두 방법을 아래에서 소개한다.


 

Thread의 서브클래스


쓰레드를 실행하는 첫 번째 방법은 Thread의 서브클래스를 만들어 run() 메소드를 오버라이딩 하는 것인데, run() 메소드는 Thread의 start() 메소드가 호출됐을 때 실행되는 메소드이다. Thread의 서브클래스를 만드는 방법은 다음과 같다.


  public class MyThread extends Thread {

    public void run(){
       System.out.println("MyThread running");
    }
  }


그리고 다음은 위 쓰레드를 실행하기 위한 코드이다.


  MyThread myThread = new MyThread();
  myTread.start();


start() 메소드는 호출이 이루어지면 곧바로 종료되며, run() 메소드가 끝나기를 기다리지 않는다. run() 메소드는 마치 다른 CPU에 의해 실행되는 것처럼 실행된다. run() 메소드가 실행될 때, "MyThread running" 문구가 출력될 것이다.


Thread의 익명 클래스를 생성할 수도 있다.


  Thread thread = new Thread(){
    public void run(){
      System.out.println("Thread Running");
    }
  }

  thread.start();


이 예제는 새로운 쓰레드에 의해 "Thread running" 문구를 출력할 것이다.


 

Runnable 인터페이스 구현


쓰레드를 실행하는 두 번째 방법은 java.lang.Runnable 인터페이스를 구현하는 것이다. Runnable 객체는 Thread에 의해 실행된다.

여기 그 예제가 있다.


  public class MyRunnable implements Runnable {

    public void run(){
       System.out.println("MyRunnable running");
    }
  }


run() 메소드가 호출되도록 하기 위해 MyRunnable 클래스의 인스턴스를 Thread의 생성자로 전달한다.


   Thread thread = new Thread(new MyRunnable());
   thread.start();


이렇게 하면 쓰레드가 시작될 때 Thread 자신의 run() 메소드 대신 생성자로 전달된 MyRunnable 인스턴스의 run() 메소드를 호출한다.위의 예제는 "MyRunnable running" 문구를 출력한다.


여기서도 역시 익명 클래스를 생성할 수 있다.


   Runnable myRunnable = new Runnable(){

     public void run(){
        System.out.println("Runnable running");
     }
   }

   Thread thread = new Thread(myRunnable);
   thread.start();

 


서브클래스와 java.lang.Runnable


두 방법 모두 잘 작동하며, 이들 중 어느쪽이 더 나은가에 대한 정답은 없다. 개인적으로는 Runnable 인터페이스를 구현하는 쪽을 선호하는데, 쓰레드 풀(thread pool)에게 Runnable의 인스턴스를 실행하게 하는 쪽이 Runnable 인스턴스를 Thread가 대기할 때까지 줄세워두기(queue up) 쉽다. 이 작업은 Thread의 서브클래스로는 약간 더 어려운 일이다.


종종 Runnable 인터페이스의 구현과 함께 Thread의 서브클래스를 만들어야 할 때가 있다. 예를 들자면 하나 이상의 Runnable 인스턴스를 실행하는 경우이다. 이런 상황이 쓰레드 풀을 구현할 때의 전형적인 케이스이다.


 

위험요소: start() 메소드가 아닌 run() 메소드의 호출


쓰레드를 생성하고 실행하는 데에 있어서 흔히 발생하는 실수는 start() 메소드 대신 run() 메소드를 실행하는 일이다.


  Thread newThread = new Thread(MyRunnable());
  newThread.run();  //should be start();


예상대로 Runnable의 run() 메소드가 실행되기 때문에 여기서 무엇이 문제인지 모를 수도 있다. 하지만 이 코드는 새로 생성된 쓰레드에 의해 실행된 것이 아니다. 여기서 실행된 run() 메소드는 이 코드를 실행하는 현재 쓰레드(current thread)에 의해 작동되는데, MyRunnable 인스턴스의 run() 메소드를 새로 생성된 쓰레드 - 코드를 실행하는 쓰레드(current thread)가 아닌 - 가 실행하도록 하기 위해서는 newThread.start() 메소드를 호출해야만 한다.


 

쓰레드의 이름


자바 쓰레드를 생성할 때 쓰레드에게 이름을 부여할 수 있다. 쓰레드의 이름은 멀티쓰레드 환경에서 각 쓰레드를 쉽게 구분할 수 있게 해준다. 예를 들어, 다수의 쓰레드가 System.out 을 사용한다고 하면, 콘솔에 찍힌 문구가 어떤 쓰레드에 의해 출력된 것인지 식별하는 데에 유용하다.


   Thread thread = new Thread("New Thread") {
      public void run(){
        System.out.println("run by: " + getName());
      }
   };


   thread.start();
   System.out.println(thread.getName());


Thread의 생성자에 전달된 "New Thead" 문구를 보자. 이 문자열이 쓰레드의 이름이다. 쓰레드는 Thread의 getName() 메소드를 통해 이 이름을 획득한다. Runnable 인스턴스를 이용한 쓰레드 생성에서도 쓰레드 이름을 지정할 수 있다.


   MyRunnable runnable = new MyRunnable();
   Thread thread = new Thread(runnable, "New Thread");

   thread.start();
   System.out.println(thread.getName());


하지만 MyRunnable 클래스는 Thread의 서브클래스가 아니기 때문에, 여기서 getName() 을 호출할 수는 없다.


 

Thread.currentThread()


Thread.currentThread() 메소드는 currentThread() 를 실행하는 Thread 인스턴스의 레퍼런스를 반환한다. 이 방법으로 자바 코드블럭을 실행하는 쓰레드의 객체에 접근할 수 있다.


Thread thread = Thread.currentThread();


Thread 객체를 가지게 되면 이 객체의 메소드를 호출할 수 있는다. 예를 들어 현재 코드를 실행중인 쓰레드의 이름은 다음과 같이 획득할 수 있다.


 

자바 쓰레드 예제


아래 작은 예제가 하나 있다. 이 코드는 먼저 main() 메소드를 실행하는 쓰레드의 이름을 출력한다. 이 쓰레드는 JVM에 의해 할당된 것이다. 그리고 다음으로는 10개의 쓰레드를 실행하고 이 쓰레드들에게 숫자로 된 이름("" + i)을 부여한다. 각 쓰레드는 자신의 이름을 출력하고는 동작이 종료된다.


public class ThreadExample {

  public static void main(String[] args){
    System.out.println(Thread.currentThread().getName());
    for(int i=0; i<10; i++){
      new Thread("" + i){
        public void run(){
          System.out.println("Thread: " + getName() + " running");
        }
      }.start();
    }
  }
}


쓰레드를 순차적으로(1, 2, 3, ...) 시작했지만 이들이 이 순서대로 실행되지 않는다는 점에 주목하자. 처음 시작된 1번 쓰레드는 콘솔에 자신의 이름을 출력하는 첫 번째 쓰레드가 되지 않을 수도 있다. 이는 쓰레드의 실행이 순차적이 아닌, 병렬적으로 이루어지기 때문이다. JVM과 명령 시스템, 혹은 JVM이나 명령 시스템(and/or)은 쓰레드의 실행 순서를 결정하고, 이 순서는 실행할 때마다 달라질 수 있다.

 

+ Recent posts