제리의 배움 기록

[Java] 프로세스와 스레드 - <2> LifeCycle 비교 본문

자바

[Java] 프로세스와 스레드 - <2> LifeCycle 비교

제리92 2021. 12. 14. 20:29

 

 

[JAVA] 프로세스와 스레드 - <1> JVM 메모리 관점에서 분석

프로세스와 스레드 프로세스는 프로그램이 실행된 상태를 의미 합니다. 스레드는 프로세스보다 작은 최소 실행 단위입니다. 모든 프로세스는 최소 하나의 실행단위 스레드를 가집니다. JVM에서

jerry92k.tistory.com

 

LifeCycle 관점에서 멀티프로세싱과 멀티스레딩 비교

멀티 프로세싱

특징

  • 하나의 프로세스에서 다른 프로세스로 전환됩니다.
  • 프로세스가 점유하고 있던 CPU regiter, RAM, cache 등이 반환되고 다른 프로세스의 데이터로 전환되어 오버헤드가 큽니다.

Process LifeCycle 흐름

New (Create)

  • 2차 메모리(디스크와 같은 장기기억 저장소)에 있는 프로그램이 OS에 의해 선택되어 프로세스가 되는 단계입니다.

Ready

  • 프로세스는 생성된 후 메인 메모리로 올라가서 ready 상태로 들어갑니다.(queue에서 대기)
  • 이 단계에서 프로세스는 실행할 준비를 하고 dispatcher에 의해 CPU에 할당되기를 대기합니다.

Run

  • 프로세스가 CPU 자원을 할당받아 실행되는 상태입니다.

Blocked or wait

  • 프로세스가 I/O 작업(네트워크나 파일, 데이터베이스 등 외부 커널 자원 요청, 사용자 입력 대기 등)을 수행중인 경우
  • critical section(임계구역)에서 lock을 획득하기를 기다리는 경우
  • 대기 작업이 끝나는 경우 Reday 상태로 돌아갑니다.

Terminated or completed

  • PCB(Process Controll Block)의 삭제와 함께 프로세스가 종료됩니다.

Suspend ready (프로세스 실행 중지)

  • ready 상태에서 메인메모리가 부족해져 2차 메모리로 이동된 상태

Suspend wait or suspend blocked (프로세스 실행 중지)

  • Blocked 상태에서 메인메모리가 부족해져 2차 메모리로 이동된 상태
  • memory image를 2차 메모리에 보관
  • Blocked에서 수행하던 작업이 끝난 이벤트가 발생하면 suspend ready 상태로 변경

Process schedulers

Long term – performance

  • 얼마나 많은 프로세스가 ready 상태에 있을지를 결정합니다.
  • 스케쥴러 동작 주기가 길기 때문에 long term scheduler라고 합니다.

Short term - Context switching time

  • 어떤 프로세스를 다음에 실행할지 결정하고 디스패처를 호출합니다.
  • 디스패처는 ready 상태에 있는 프로세스를 CPU에 할당하여 실행될 수 있도록 합니다. 동시에 기존에 해당 CPU를 점유하고 있던 프로세스는 ready(timeout) or blocked(event wait) or release 상태로 변경됩니다.
  • 이 과정을 context switching 이라 합니다.

Medium term – Swapping time

  • 메인메모리와 2차 메모리 사이에서 프로세스를 swap 하는 결정을 합니다.

멀티 스레딩

특징

  • 동일 프로세스 내에서 하나의 스레드에서 다른 스레드로 전환됩니다.
  • 프로세스가 점유하고있는 메모리 공간은 변경되지 않습니다.
  • 해당 스레드의 실행 흐름을 관리하기 위한 program counter, cpu register, stack pointer 등만 변경되면 되어 프로세스의 컨텍스트 스위칭보다 훨씬 경제적입니다.

Thread LifeCycle 흐름

New : 스레드가 생성되었지만 아직 실행되지 않은 상태

  • 새로운 스레드 객체를 생성한 시점(start() 호출 전)
public class FileWatcherThread extends Thread {
...
    @Override
    public void run() {
    ...
    }
}
FileWatcherThread fileWatcher = new FileWatcherThread();

Runnable : 현재 CPU를 점유하고 작업을 수행 중인 상태

  • 스레드의 start() 메서드를 호출하면 New에서 Runnable 상태로 변경됩니다.
  • 멀티스레딩 환경에서는 스레드 스케줄러 알고리즘에 따라 스레드의 실행순서가 결정됩니다.
    따라서 스레드는 특정 시간동안에 실행 후 다른 Runnable 스레드에게 자원을 양보합니다.
  • 스케쥴링에 따라 start() 호출시 항상 즉시 실행되는걸 보장 할 수 없습니다. 경우에 따라 CPU를 즉시 점유할 수 없는 상황이면 ready to run 에서 대기합니다.
fileWatcher.start();

Blocked

  • 다른 스레드가 lock을 설정한 코드에 진입하기 위해 monitor lock을 기다리는 상태
public class FileWatcherThread extends Thread {
    @Override
    public void run() {
        accessCriticalSection();
    }

    // 한번에 하나의 스레드만 접근 가능하도록 동기화
    public static synchronized void accessCriticalSection() {
        doSomething();
    }
}

FileWatcherThread fileWatcher1 = new FileWatcherThread();
FileWatcherThread fileWatcher2 = new FileWatcherThread();
fileWatcher1.start(); // fileWatcher1이 먼저 start()를 호출하게 되면서 임계 구간이 진입한 상태
fileWatcher2.start(); // fileWatcher1 작업이 끝날 때 까지 fileWatcher2는 Blocked

Waiting : 다른 스레드가 특정한 액션을 취하기를 기다리는 상태

  • 스레드는 다음 세가지 메서드 중 하나를 호출하면 Waiting 상태로 변합니다.
  • 1)object.wait(), 2)thread.join(), 3)LockSupport.park()
  • wait()과 join()은 타임아웃이 별도로 성정되지 않습니다.
public class WatcherMain {

    public static void main(String[] args) {

        FileWatcherThread fileWatcher = new FileWatcherThread();
        fileWatcher.start();
        try {
            fileWatcher.join(); // fileWatcher 스레드가 종료되기를 대기
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Timed_Wating

  • Waiting 상태와의 차이점은 메서드의 인수로 최대 대기 시간을 명시할 수 있어 외부적인 변화뿐만 아니라 시간에 의해서도 Waiting 상태가 해제될 수 있습니다.
  • 다음 5가지 방법은 timed_wating 상태로 스레드를 변화시킵니다.
  • 1)thread.sleep(long millis), 2)wait(int timeout) or wait(int timeout, int nanos)
    3)thread.join(long millis), 4)LockSupport.parkNanos, 5)LockSupport.parkUntil

Interrupt

  • interrupt()는 Non-Runnable(Blocked, Timed_Wating, Waiting) 상태의 스레드를 Runnable 상태로 변경하면서 InterruptedException 예외를 발생시킵니다.
    • NEW, TERMINATED 상태에선 영향받지 않습니다.
    •  Runnable 상태일 경우에는 이벤트가 예약되어 추후 Non-Runnable 상태로 변하였을때 interrupt가 발생합니다.
    • JDK11, Thread.java 참고
      
      If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, 
      or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, 
      then its interrupt status will be cleared and it will receive an InterruptedException.
      ...
      Interrupting a thread that is not alive need not have any effect.
  • 이를 이용해서 1)스레드의 실행 흐름을 제어하거나 2)즉시 종료할 수 있습니다.
  • 이전에는 스레드의 즉시 종료를 위해 stop() 메서드를 사용하였으나, 자원 반납이 정상적으로 이루어지지 않는 등 안정적이지 않은 종료 문제로 현재 deprecate 되었습니다.

isInterrupted()와 interrupted() 차이

공통점 : 인터럽트가 예약되어 있는지 확인 및 예약되어 있는 인터럽트 취소.

차이점

  • boolean isInterrupted() : 다른 스레드에서 확인할 때 사용
  • static boolean interrupted() : 본인 스레드를 확인할 때 사용

예시1. sleep 상태인 스레드를 interrupt 하였을때

public class FileWatcherThread extends Thread {
    @Override
    public void run() {

        int count=0;
        while (true){
            System.out.println(String.format("count : %d",count++));
            long startTime = System.currentTimeMillis();
            try {
                sleep(3000); // Non-Runnable 상태가 되면 interrupt 발생
            } catch (InterruptedException e) {
                System.out.println("interrupted");
            }
            System.out.println(String.format("time takes :%d",System.currentTimeMillis()-startTime));
            doSomething();
        }
    }
}

public class WatcherMain {
    public static void main(String[] args) {
        FileWatcherThread fileWatcher =new FileWatcherThread();
        fileWatcher.start();

        fileWatcher.interrupt(); // interrupt 발생시킴
    }
}

[결과]

  • 첫번째 시도에서(1:) sleep() 중일때, demonThreadB.interrupt()가 호출되어 InterruptedException이 발생합니다.
    (2:)sleep 중간에 interrupt 되어 소요된 시간은 sleep한 3000 mills이 아니고 1 mills로 측정됩니다.
  • 두번째 시도부터는 interrupt가 없기 때문에 3000 miliis 만큼 sleep 합니다.(5:, 7:)
1: count : 0
2: interrupted 
3: time takes :1
4: count : 1
5: time takes :3006
6: count : 2
7: time takes :3001
8: count : 3
...

1-1. interrupt시 while문을 탈출하고 싶으면, boolean 값을 while문 조건으로 사용하고 interrupt에서 boolean 값을 변경하는 식으로 구현할 수 있습니다.

boolean keepRunning=true;
while(keepRunning){
..
    try {
        sleep(3000); 
    } catch (InterruptedException e) {
        keepRunning=false;
    }

1-2. 혹은 다른 작업을 수행할 수도 있습니다.

...
    } catch (InterruptedException e) {
        doOtherOperation();
        return; // 스레드 종료
    }
...

 

예시2. catch에서 자연스럽게 종료

    @Override
    public void run() {
        try {
        int count = 0;
            while (true) {
                System.out.println(String.format("count : %d", count++));
                long startTime = System.currentTimeMillis();
                sleep(3000); 
                System.out.println(String.format("time takes :%d", System.currentTimeMillis() - startTime));
            }
        } catch (InterruptedException e) { // catch에서 별다른 처리를 하지 않아도 자연스럽게 종료
            System.out.println("interrupted");
        }
    }

[출력결과]

  • 첫번째 sleep 중 interrupt가 발생하여 while문 탈출 및 스레드 정상종료
1: count : 0
2: interrupted 

 

[참고]
difference between thread context switch and process context switch
baeldung - java-thread-lifecycle
naver d2 - 스레드 덤프
geeksforgeeks - states of process

Comments