[기본 개념] 8 | (1.1) 프로세스, 스레드, Start( ), Run( ), 싱글/멀티 스레드

728x90

[기본 개념] 8 | (1.1) 프로세스, 스레드, Start( ), Run( ), 싱글/멀티 스레드

1> 프로세스와 쓰레드

2> 쓰레드의 구현과 실행

3> start( )와 run( )

4> 싱글쓰레드와 멀티쓰레드

5 쓰레드의 우선순위

6 쓰레드 그룹(thread group)

7 데몬 쓰레드(daemon thread)

8 쓰레드의 실행제어

9 쓰레드의 동기화

    9.1 synchronized를 이용한 동기화

    9.2 wait( )와 notify( )

    9.3 Lock과 Condition을 이용한 동기화

    9.4 volatile

    9.5 fork & join 프레임웍

1. 프로세스와 쓰레드

 프로세스란 '실행 중인 프로그램'이다. 프로그램을 실행하면 OS로부터 실행에 필요한 자원(메모리)을 할당받아 프로세스가 된다.

 

 프로세스는 프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원, 쓰레드로 구성되어 있으며, 프로세스의 자원을 이용해서 작업을 수행하는 것이다.

 

 모든 프로세스에는 최소한 하나 이상 쓰레드가 필요하다. 하나의 쓰레드를 가진 프로세스를 싱글쓰레드 프로세스라고 하고, 둘 이상의 쓰레드를 가진 프로세스를 멀티쓰레드 프로세스라고 한다.

 

멀티태스킹과 멀티쓰레딩

 멀티태스킹은 윈도우나 유닉스같이 여러 개의 프로세스를 동시에 실행할 수 있는 것이고, 멀티쓰레딩은 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업을 수행하는 것이다.

 

 프로세스의 성능은 쓰레드에 개수에 비례하지 않으며, 오히려 하나보다 두 개의 쓰레드를 가진 프로세스가 더 낮은 성능을 보일 수 있다.

 

멀티쓰레딩의 장단점

1 CPU의 사용률을 향상시킨다.

2 자원을 보다 효율적으로 사용할 수 있다.

3 사용자에 대한 응답성이 향상된다.

4 작업이 분리되어 코드가 간결해진다.

2. 쓰레드의 구현과 실행

 쓰레드를 구현하는 방법은 2가지가 있다.

 

1 Thread클래스를 상속받는 방법    --> Thread클래스 이외에 다른 클래스를 상속받을 수 없음

 

class MyThread extends Thread { 

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

}

 

2 Runnable인터페이스를 구현하는 방법

 

class MyThread implements Runnable { 

     public void run( )    { /* 작업내용 */ }      // Runnable인터페이스의 run( )을 구현

}

 

예제/ThreadEx1.java

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);

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

class ThreadEx1_1 extends Thread {
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName());
        }
    }
}

class ThreadEx1_2 implements Runnable {
    public void run() {
        for (int i = 0; i < 5; i++) {
            // Thread.currentThread() - 현재 실행중인 Thread를 반환
            System.out.println(Thread.currentThread().getName());
        }
    }
}
실행결과

Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1

 

 Runnable인터페이스를 구현한 경우, 구현한 클래스의 인스턴스를 생성한 다음, 이 인스턴스를 Thread클래스의 생성자의 매개변수로 제공해야 한다.

 

 Thread클래스를 구현한 경우, 자손 클래스에서 메서드를 직접 호출할 수 있지만, Runnable인터페이스를 구현한 경우, currentThread( )를 호출하여 쓰레드에 대한 참조를 얻어야 호출이 가능하다.

 

쓰레드의 실행 - start( )

 쓰레드를 생성한 후 start( )를 호출해야 실행된다.

 

 start( )가 호출됐다 해서 바로 실행되는 것이 아니라 실행 대기 상태에 있다가 자기 차례가 되어야 실행된다.

 

 또한 한번 실행이 종료된 쓰레드는 다시 실행할 수 없다. 한번 더 수행해야 한다면, 다시 새로운 쓰레드를 생성한 다음 start( )를 호출해야 한다. 그렇지 않으면 IllegalThreadStateException이 발생한다.

 

ThreadEx1_1 t1 = new ThreadEx1_1( ) ;

t1.start( ) ;

t1 = new ThreadEx1_1( ) ;    // 다시 생성

t1.start( ) ;          // OK.

3. start( )와 run( )

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

 

 run( )은 단순히 클래스에 선언된 메서드를 호출하는 것이다.

 

1 main메서드에서 쓰레드의 start( )를 호출한다.

2 start( )는 새로운 쓰레드를 생성하고, 쓰레드가 작업하는 데 사용될 호출스택을 생성한다.

3 새로 생성된 호출스택에 run( )이 호출되어, 쓰레드가 독립된 공간에서 작업을 수행한다.

4 이제는 호출스택이 2개므로 스케줄러가 정한 순서에 의해 번갈아 가면서 실행된다.

 

 주어진 시간 동안 작업을 마치지 못한 쓰레드는 다시 차례가 돌아올 때까지 대기상태로 있으며, run( )의 수행이 종료된 쓰레드는 호출스택이 비워지며 사라진다.

 

main쓰레드

 main메서드의 작업을 수행하는 쓰레드를 main쓰레드라고 한다.

 

 main메서드가 수행을 마쳤다 하더라도 다른 쓰레드가 작업을 마치지 않았다면 종료되지 않는다.

 

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

4. 싱글쓰레드와 멀티쓰레드

 두 개의 작업을 하나의 쓰레드로 처리하는 경우와 두 개의 쓰레드로 처리하는 경우를 가정해보자.

 

 싱글쓰레드 프로세스인 경우, 한 작업을 마친 후 다른 작업을 시작한다.

 


th1  


|                                                  A                                                  |


|                                                 B                                                 |

        t1
      t2

하나의 쓰레드로 두 개의 작업을 수행하는 경우

 

 멀티쓰레드 프로세스인 경우, 짧은 시간 동안 2개의 쓰레드가 번갈아가면서 작업을 수행하여 동시에 처리되는 것같이 느끼게 한다.

 


th1  
 
|          A          |
  |          A          |   |          A          |   |          A          |  

th2  
 
  |          B          |   |          B          |   |          B          |   |          B          |
              t1 t2

두 개의 쓰레드로 두 개의 작업을 수행하는 경우

 

 싱글쓰레드 프로세스와 멀티쓰레드 프로세스의 작업 수행한 시간(t2)은 거의 같다. 오히려 쓰레드간의 작업전환에 시간이 더 걸리기 때문에 멀티쓰레드 프로세스로 작업한 시간이 더 길다.

 

 새로운 쓰레드 하나 생성해서 두 개의 쓰레드가 작업을 나누어 수행한 후 실행결과를 비교해보자.

 

 싱글 코어인 경우, 멀티쓰레드라도 하나의 코어가 번갈아가면서 작업을 수행하는 것이므로 두 작업이 겹치지 않는다.

 


th1  
 
|          A          |   |          A          |   |          A          |   |          A          |  

th2  

  |          B          |   |          B          |   |          B          |   |          B          |
              t1 t2

싱글 코어로 두 개의 쓰레드를 실행하는 경우

 

 멀티 코어인 경우, 멀티쓰레드로 두 작업을 수행하면, 동시에 두 쓰레드가 수행될 수 있으므로 겹치는 부분이 발생한다.

 


th1  

|          A          |   |          A          |   |          A          |
  |          A          |  

th2  

  |          B          |   |          B          |   |          B           |   |          B          |  
          t1 t2    

싱글 코어로 두 개의 쓰레드를 실행하는 경우

 

 두 쓰레드가 서로 다른 자원을 사용하는 작업의 경우 싱글쓰레드 프로세스보다 멀티쓰레드 프로세스가 더 효율적이다. 두 개의 쓰레드로 처리한다면 사용자의 입력을 기다리는 동안 다른 쓰레드가 작업을 처리할 수 있기 때문이다.

 

 

 

 

 

 

출처 | Java의 정석 (남궁 성)

728x90