java

java_chapter13_쓰레드

강용민 2023. 3. 11. 22:04

1. 프로세스와 쓰레드

  • 프로세스(process) : 실행 중인 프로그램
  • 쓰레드(thread) : 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것

현재 우리가 사용하는 대부분의 OS는 멀티태스킹(multi-tasking)을 지원하기 때문에 여러 개의 프로세스가 동시에 실행될 수 있다. 또한 멀티쓰레딩은 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업을 수행하는 것이다.

 

멀티쓰레딩의 장단점

  • 장점
    • CPU의 사용률을 향상시킨다.
    • 자원을 보다 효율적으로 사용할 수 있다.
    • 사용자에 대한 응답성이 향상된다.
    • 직업이 분리되어 코드가 간결해진다.
  • 단점
    • 같은 프로세스 내에서 자원을 공유하면서 작업을 하기 때문에 발생할 수 있는 동기화
    • 교착상태

 

2. 쓰레드의 구현과 실행

쓰레드를 구현하는 방법은 Thread클래스를 상속받는 방법과 Runnable인터페이스를 구현하는 방법으로 나뉜다. 보통 상속문제로 인해 Runnable인터페이스를 구현하는 방법이 일반적이다.

Runnable인터페이스를 구현하는 방법은 재사용성이 높고 코드의 일관성을 유지할 수 있기 때문에 보다 객체지향적인 방법이라 할 수있다.

Runnable 인터페이스는 오로지 run()만 정의되어 있는 간단한 인터페이스이다.

class ThreadEx1 {
    public static void main(String args[]){
        TrheadEx1_1 t1 = new ThreadEx1_1();
        
        Runnable r = new ThreadEx1_2();
        Thread t2 = new Thread(r);
        
        t1.start();
        t2.start();
    }
}

class TreadEx1_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++){
             System.out.println(THread.currentThread().getName());
        }
    }
}

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

주의할 점은 한 번 실행이 종료된 쓰레드는 다시 실행할 수 없다. 즉, 하나의 쓰레드에 대해 start()가 한 번만 호출될 수 있다는 뜻이다. 만얀 한 번 더 수행해야 한다면 새로운 쓰레드를 생성한 다음에 start()를 호출해야 한다.

 

3. start()와 run()

  • run () : 단순히 클래스에 선언된 메서드를 호출하는 것일 뿐이다.
  • start() : 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택(call stack)을 생성한 다음에 run()을 호출해서, 생성된 호출스택에 run()이 첫 번쨰로 올라가게 한다.

스케줄러는 실행대기중인 쓰레드들의 우선순위를 고려하여 실행순서와 실행시간을 결정하고, 각 쓰레드들은 작성된 스케줄에 따라 자신의 순서가 되면 지정된 시간동안 작업을 수행한다. 만약 시간 내에 끝마치지 못했다면 다시 자신의 차례까지 기다려야 한다.

 

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

하나의 쓰레드로 두 작업을 처리하는 경우는 한 작업을 마친 후 다른 작업을 시작하지만, 두 개의 쓰레드로 작업 하는 경우에는 짧은 시간동안 2개의 쓰레드가 번갈아 가면서 작업을 수행하기에 동시에 작업을 하는 느낌을 준다.하지만 두 개의 쓰레드로 작업을 하면 시간이 더 걸리는데 그 이유는 쓰레드간의 작업전환(context switching)에 시간이 걸리기 때문이다.

 

5. 쓰레드의 우선순위

쓰레드는 우선순위라는 속성을 가지고 있는데, 이 우선순위의 값에 따라 쓰레드가 얻는 실행시간이 달라진다. 쓰레드가 수행하는 작업의 중요도에 따라 쓰레드의 우선순위르 ㄹ서로 다르게 지정하여 특정 쓰레드가 더 많은 작업시간을 갖도록 할 수 있다. 시각적인 부분이나 사용자에게 빠르게 반응해야하는 작업을 하는 쓰레드의 우선 순위는 다른 작업을 수행하는 쓰레드에 비해 높아야 한다.

 

쓰레드의 우선순위 지정하기

쓰레드의 우선순위와 관련된 메서드와 상수는 다음과 같다.

void setPriority(int newPriority)
int getPriority()

public static final int MAX_PRIORITY = 10
public static final int MIN_PRIORITY = 1
public static final int NORM_PRIORITY = 5

 

7. 데몬 쓰레드(deamon thread)

데몬 쓰레드는 다른 일반 쓰레드의 작업을 돕는 보조적인 영할을 수행하는 쓰레드이다. 예로는 가비지 컬렉터, 워드프로세서의 자동저장, 화면자동갱신 등이 있다.

데몬 쓰레드는 무한루프와 조건문을 이용해서 실행 후 대기하고 있다가 특정 조건이 만족되면 작업을 수행하고 다시 대기하도록 작성한다. 데몬 쓰레드는 일반 쓰레드의 작성방법과 실행방법이 같으며 다만 쓰레드를 생성한 다음 실행하기 전에 setDeamon(true)를 호출하기만 하면 된다. 그리고 데몬 쓰레드가 생성한 쓰레드는 자동적으로 데몬 쓰레드가 된다.

boolean isDamon()
void setDeamon(boolean on)

 

8. 쓰레드의 실행제어

쓰레드 프로그래밍이 어려운 이유는 동기화와 스케줄링 때문이다. 쓰레드의 스케줄링을 잘하기 위해서는 쓰레드의 상태와 관련 메서드를 잘 알아야 한다. 메서드는 필요할 때 직접 찾아보면 되니 쓰레드의 상태에 대해서 살펴보자.

  • NEW : 쓰레드가 생서오디고 아직 start()가 호출도지 않은 상태
  • RUNNABLE : 실행 중 또는 실행 가능한 상태
  • BLOCKED : 동기화블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태)
  • WAITING, TIMED_WAITING : 쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은 일시정지 상태. TIMED_WAITING은 일시정지시간이 지정된 경우를 의미한다.
  • TERMINATED : 쓰레드의 작업이 종료된 상태

다음 그림은 쓰레드의 생성부터 소멸까지의 모든 과정을 그린 것이다.

  1. 쓰레드를 생성하고 start()를 호출하면 바로 실행되는 것이 아니라 실행대기열에 저장되어 자신의 차례가 될 때까지 기다려야 한다. 실행대기열은 큐와 같은 구조로 먼저 실행대기열에 들어온 쓰레드가 먼저 실행된다.
  2. 실행대기상태에 있다가 자신의 차례가 되면 실행상태가 된다.
  3. 주어진 실행시간이 다되거나 yield()를 만나면 다시 실행대기상태가 되고 다음 차례의 쓰레드가 실행상태가 된다.
  4. 실행 중에 일시정지 상태가 될 수 있다.
  5. 지정된 일시정지시간이 다되거나 호출되면 일시정지상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다리게 된다.
  6. 실행을 모두 마치거나 stop()이 호출되면 쓰레드는 소멸된다.

 

9. 쓰레드의 동기화

싱글쓰레드 프로세스의 경우 프로세스 내에서 단 하나의 쓰레드만 작업하기 때문에 프로세스의 자원을 가지고 작업하는데 별문제가 없지만, 멀티쓰레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업하기 떄문에 서로의 작업에 영향을 주게된다.이러한 일이 발생하는 것을 방지하기 위해서 한 쓰레드가 특정 작업을 끝마치기 전까지 다른 쓰레드의 의해 방해받지 않도록 하는 것이 필요하다. 그래서 도입된 개념이 바로 '임계 영역(critical section)'과 '잠금(lock)'이다.

공유 데이터를 사용하는 코드 영역을 임계 영역으로 지정해놓고, 공유 데이터(객체)가 가지고 있는 lock을 획득한 단 하나의 쓰레드만 이 영역 내의 코드를 수행할 수 있게 한다. 그리고 해당 쓰레드가 임계영역 내의 모든 코드를 수행하고 벗어나서 lock을 반납해야만 다른 쓰레드가 반납된 lock을 획득하여 임계 영역의 코드를 수행할 수 있게 된다.

이처럼 한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 '쓰레드의 동기화(synchronization)'이라 한다.

 

9.1 synchronized를 이용한 동기화

이 키워드는 임계 영역을 설정하는데 사용된다. 아래와 같이 두 가지 방식이 있다.

// 메서드 전체를 임계 영역으로 지정
public synchronized void calcSum() {
...
}

// 특정한 영역을 임계 영역으로 지정
synchronize(객체의 참조변수){
...
}

임계 영역은 멀티쓰레드 프로그램의 성능을 좌우하기 때문에 가능하다면 메서드 전체에 락을 거는 것보다 synchronized블럭으로 임계 영역을 최소화해서 보다 효율적인 프로그램이 되도록 노력해야 한다.

 

문제.

멀티쓰레딩의 장단점은 무엇인가??

- 장점 : cpu의 사용률을 향상시켜 자원을 보다 효율적으로 사용할 수 있다. 또한 사용자의 응답성을 높일 수 있다.

- 단점 : 동기화 문제 및 교착상태

 

그럼 멀티쓰레드가 무조건 좋은가?

- 아니다. 멀티쓰레드는 실제로 동시에 여러 가지 작업을 처리하는것이 아닌 여러 개의 작업을 짧은 시간동안 반복하는 것이다. 그렇기에 쓰레드간 변경(context switch)시간이 걸린다. 또한 동기화문제가 있기에 특정한 상황(CPU만 계산하는 상황)일때는 싱글 쓰레드가 더 효과적이다.

 

데몬 쓰레드는 무엇인가??

데몬 쓰레드는 보조 쓰레드이다. 특정 조건이 만족하면 실행했다가 마치면 다시 대기한다.