[Java] Thread(쓰레드) - 2

Java / / 2022. 5. 30. 23:52

1.  쓰레드의 구현과 실행

쓰레드를 구현하는 방법은 Thread 클래스를 상속 받는 방법과 Runnable 인터페이스를 구현하는 방법 2가지이다. 두가지 방법 모두 상관없지만 Thread 클래스를 상속 받으면 다른 클래스를 상속받을 수 없기 때문에 Runnable 인터페이스를 구현하는 방법이 일반적이다.

 

class MyThread extends Thread {
    public void run() { /* 작업 내용 */ } // Thread 클래스의 run()을 오버라이딩
}
class MyThread implements Runnable {
    public void run() { /* 작업 내용 */ } // Thread 클래스의 run()을 오버라이딩
}

 

2개의 방식으로 쓰레스를 만들고 호출해본다.  

package thread;

public class ThreadEx1 {

    public static void main(String[] args) {
        ThreadEx1_1 t1 = new ThreadEx1_1();
        Runnable r = new ThreadEx1_2();
        Thread t2 = new Thread(r); // 생성자 Thread(Runnable target)

        t1.start();
        t2.start();
    }

    static class ThreadEx1_1 extends Thread {
        @Override
        public void run() {
            for(int i=0;i < 5; i++) {
                System.out.println(getName()); // 조상 Thread의 getName 호출
            }
        }
    }

    static class ThreadEx1_2 implements Runnable {
        @Override
        public void run() {
            for(int i=0;i < 5; i++) {
                System.out.println(Thread.currentThread().getName()); // 현재 실행중인 Thread 이름 출력
            }
        }
    }
}

다음과 같은 실행 결과가 나타날 것이다. 

교차로 실행되야하는게 맞지 않나해서 여러번 실행해보았다. 이름을 구분하기 쉽게 setName() 메소드를 이용해서 지정해주었다. 여러번 실행하다보니 교체로 실행되는 것도 확인이 가능하다.

Runnable 방식으로 Thread를 생성할 때 Thread의 생성자로 Runnable 객체를 넣어주는데 Thread 클래스를 간단히 축약해서 보면 아래 코드와 같다.

public class Thread {

    private Runnable r;
    
    public Thread(Runnable r) {
        this.r = r;
    }
    
    public void run() {
        if(r != null)
            r.run(); // Runnable 인터페이스를 구현한 인스턴스의 run()을 호출
    }

}

 

쓰레드의 이름은 다음과 같은 생성자나 메서드를 통해서 지정 또는 변경할 수 있다.

Thread(Runnable target, String name)
Thread(String name)
void setName(String name)

 

쓰레드를 실행하기 위해서는 start() 메소드를 사용한다. start() 메소드를 호출해야만 쓰레드가 실행되는 것 이다. 사실 start() 메소드를 호출했다고 바로 실행되는 것은 아니고 일단 실행 대기 상태에 있다가 자신의 차례가 되어야 실행된다. 물론 실행 대기중인 쓰레드가 하나도 없으면 곧바로 실행된다. 한가지 더 알아두어야 할 것은 한 번 실행이 종료된 쓰레드는 다시 실행할 수 없다. start() 를 두 번 호출하면 IllegalThreadStateException이 발생한다.

 

2.  start()와 run()

쓰레드를 실행할 때 run()이 아닌 start()를 호출하는 것에 대해서 다소 의문이 들 수 있다. main 메서드에서 run()을 호출하는 것은 생성된 쓰레드를 실행하는 것이 아니라 단순히 클래스에 선언된 메소드를 호출하는 것이다.

 

반면에 start()는 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택(call stack)을 생성한 다음에 run()을 호출해서, 생성된 호출스택에 run()이 첫 번째로 올라가게 한다.

 

모든 쓰레드는 독립적인 작업을 수행하기 위해 자신만의 호출 스택을 필요로 하기 때문에 새로운 쓰레드를 생성하고 실행할 때마다 새로운 호출 스택이 생성되고 쓰레드가 종료되면 작업에 사용된 호출스택은 소멸된다.

 

  • (1) main 메서드에서 쓰레드의 start()를 호출한다.
  • (2) start()는 새로운 쓰레드를 생성하고 쓰레드가 작업하는데 사용될 호출 스택을 생성한다.
  • (3) 새로 생성된 호출스택에 run()이 호출되어, 쓰레드가 독립된 공간에서 작업을 수행한다.
  • (4) 이제는 호출스택이 2개이므로 스케줄러가 정한 순서에 의해서 번갈아 가면서 실행된다.

호출 스택에서는 가장 위에 있는 메서드가 현재 실행중인 메서드이고 나머지 메서드들은 대기상태에 있다는 것을 기억하고 있을 것 이다. 그러나 위의 그림에서와 같이 쓰레드가 둘 이상일 때는 호출스택의 최상위에 있는 메서드 일지라도 대기상태에 있을 수 있다. 스케줄러가 우선순위를 고려해서 실행시간을 결정하고, 각 쓰레드들은 작성된 스케줄러에 의해서 수행된다.

 

이때 주어진 시간동안 작업을 마치지 못하면 쓰레드는 다시 자신의 차례가 돌아올 때까지 대기 상태로 있게된다. run()의 수행이 종료된 쓰레드는 호출 스택이 모두 비워지면 사라진다. main메서드가 호출스택이 비워지면 프로그램도 종료되는 것과 같다.

 

3.  main 쓰레드

main 메서드의 작업을 수행하는 것도 쓰레드이며, 이를 main 쓰레드라고 한다. 우리는 지금까지 우리도 모르게 쓰레드를 사용하고 있었다. 프로그램이 실행되기 위해서는 작업을 수행하는 일꾼이 최소한 하나는 필요하다.

실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.

 

4.  Thread stack 확인

아래 코드를 수행해보면 호출 스택의 첫 번째 메서드가 main 메서드가 아니라 run 메서드인 것을 확인할 수 있다. 한 쓰레드가 예외가 발생해서 종료되어도 다른 쓰레드의 실행에 영향을 미치지 않는다. 

public class ThreadEx2 {

    public static void main(String[] args) {
        ThreadEx2_1 t1 = new ThreadEx2_1();
        t1.start();
    }

    static class ThreadEx2_1 extends Thread {
        @Override
        public void run() {
            throwException();
        }

        public void throwException() {
            try {
                throw new Exception();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

[실행 결과]

java.lang.Exception
	at thread.ThreadEx2$ThreadEx2_1.throwException(ThreadEx2.java:18)
	at thread.ThreadEx2$ThreadEx2_1.run(ThreadEx2.java:13)

 

이번에느 run() 메서드를 호출해서 결과를 봐보자. 이전 예제와는 다르게 새로운 쓰레드가 생성되지 않고, main 쓰레드의 호출 스택에서 수행한 것을 볼 수 있다.

package thread;

public class ThreadEx3 {

    public static void main(String[] args) {
        ThreadEx3_1 t1 = new ThreadEx3_1();
        t1.run();
    }

    static class ThreadEx3_1 extends Thread {
        @Override
        public void run() {
            throwException();
        }

        public void throwException() {
            try {
                throw new Exception();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

 

[실행 결과]

java.lang.Exception
	at thread.ThreadEx3$ThreadEx3_1.throwException(ThreadEx3.java:18)
	at thread.ThreadEx3$ThreadEx3_1.run(ThreadEx3.java:13)
	at thread.ThreadEx3.main(ThreadEx3.java:7)

Reference

http://www.yes24.com/Product/Goods/24259565

 

Java의 정석 - YES24

최근 7년동안 자바 분야의 베스트 셀러 1위를 지켜온 `자바의 정석`의 최신판. 저자가 카페에서 12년간 직접 독자들에게 답변을 해오면서 초보자가 어려워하는 부분을 잘 파악하고 쓴 책. 뿐만 아

www.yes24.com

 

'Java' 카테고리의 다른 글

[Java] Thread(쓰레드) - 4  (0) 2022.06.02
[Java] Thread(쓰레드) - 3  (0) 2022.05.31
[Java] Thread(쓰레드) - 1  (0) 2022.05.30
[Java] 코딩 컨밴션 (Code Conventions )  (0) 2021.04.11
인텔리제이 javadoc 문서 만들기  (0) 2021.02.22
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기