프로세스와 쓰레드(2)

김운채·2023년 5월 30일
0

TIL

목록 보기
18/22

쓰레드의 우선순위

쓰레드는 우선순위라는 속성(멤버변수)을 가지고 있는데, 이 우선순위의 값에 따라 쓰레드가 얻는 실행시간이 달라진다.
작업의 중요도에 따라 쓰레드의 우선순위를 다르게하여 특정 쓰레드가 더 많은 작업을 갖게 할 수 있다.

우선순위 지정하기

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	// 보통우선순위. 기본 값

쓰레드가 가질 수 있는 범위는 1~10이며 숫자가 높을수록 우선순위가 높다.
winOS 에서는 32단계로 나눠져있다.
(우리가 정하는건 희망사항일 뿐, 실제로는 그렇게 돌아가지는 못함)

쓰레드의 우선순위는 쓰레드를 생성한 쓰레드로부터 상속받는다. 만약 main 메서드를 수행하는 쓰레드는 우선순위가 5이므로 main 메서드 내에서 생성하는 쓰레드의 우선순위는 자동적으로 5가 된다.

📌 멀티 쓰레드 프로그래밍에서 순위를 정하는 것을 쓰레드 스케줄링이라고 한다.

  • 우선순위 스케줄링 : 쓰레드의 우선순위가 높은 순으로 실행 상태를 더 많이 차지하는 스케줄링 방식이다. 개발자가 setPriority() 메소드를 사용하여 우선순위를 설정할 수 있다.

  • 라운드 로빈 : Time Slice를 정해서 그 시간만큼만 쓰레드가 실행되도록 하고 이후에는 다른 쓰레드가 실행되는 스케줄링 방식이다. JVM에 의해 결정되기 때문에 개발자가 임의로 수정이 불가능하다.

멀티코어환경에서 쓰레드의 우선순위에 따른 차이는 전혀없다.

그저 이론적으로만 "쓰레드에 높은 우선순위를 줄경우 더많은 실행시간과 실행기회를 갖게된다"는 것을 기대하는 것이다.

우선순위에 차등을 두어 쓰레드를 실행하려면, OS의 스케줄링 정책과 JVM의 구현을 직접 확인해봐야한다. 자바의 쓰레드의 우선순위와 관련된 구현이 JVM마다 차이가 있을 수 있기 때문이다. JVM을 확인하더라도 쓰레드는 OS의 스케줄러에 종속적이기 떄문에 어느정도 예측만 가능하고 정확히 알수는 없다.

쓰레드 그룹

쓰레드 그룹은 서로 관련된 쓰레드를 그룹으로 묶어서 관리하기 위한 것이다.

사실 쓰레드 그룹은 보안상의 이유로 도입된 개념으로, 자신이 속한 쓰레드 그룹이나 하위 쓰레드 그룹은 변경할 수 있지만 다른 쓰레드 그룹의 쓰레드를 변경할 수는 없다.

ThreadGroup 을 사용해서 생성할 수 있으며, 주요 생성자와 메서드는 다음과 같다.

새로운 스레드를 스레드 그룹에 포함 시키려면 스레드 클래스의 생성자 오버로딩을 이용하면 된다.

Thread(ThreadGroup group, Runnable target);
Thread(ThreadGroup group, Runnable target, String name);
Thread(ThreadGroup group, Runnable target, String name, long stackSize);
Thread(ThreadGroup group, String name);

stackSize는 JVM이 이 스레드에 할당할 스택 사이즈를 의미한다.

모든 쓰레드는 반드시 쓰레드 그룹에 포함되어있어야 하기 때문에, 위와 같이 쓰레드 그룹을 지정하는 생성자를 사용하지 않은 쓰레드는 기본적으로 자신을 생성한 쓰레드와 같은 쓰레드 그룹에 속하게 된다.

자바 어플리케이션이 실행되면, JVM은 main과 system이라는 쓰레드 그룹을 만들고 JVM운영에 필요한 쓰레드들을 생성해서 이 쓰레드 그룹에 포함시킨다.
예를 들어, main 메서드를 수행하는 main이라는 이름의 쓰레드는 main 쓰레드 그룹에 속하고, 가비지컬렉션을 수행하는 Finalizer 쓰레드는 system 쓰레드 그룹에 속한다.

우리가 생성하는 모든 쓰레드 그룹은 main쓰레드 그룹의 하위쓰레드 그룹이 되며, 쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 자동적으로 main 쓰레드 그룹에 속하게 된다.

  • 쓰레드 그룹 관련 메서드
ThreadGroup getThreadGroup()	// 쓰레드 자신이 속한 쓰레드 그룹을 반환한다.
void uncaughtException(Thread t, Throwable e) // 처리되지 않은 예외에 의해 
// 쓰레드 그룹의 쓰레드가 실행이 종료되었을 때, JVM에 의해 이 메서드가 자동적으로 호출된다.

데몬 쓰레드(daemon thread)

쓰레드는 데몬 쓰레드Daemon Thread, 일반 쓰레드Normal Thread 두 가지 종류가 있다. JVM이 시작되면 생성되는 모든 쓰레드는 메인 쓰레드를 제외한 모두가 데몬 쓰레드이다. 쓰레드는 해당 쓰레드를 생성한 쓰레드의 상태를 상속받으므로 메인 쓰레드에 의해 만들어지는 쓰레드는 일반 쓰레드이다.

데몬 쓰레드는 데몬 쓰레드가 아닌 쓰레드, 즉 일반 쓰레드의 작업을 돕는 보조적 역할을 담당하는 쓰레드이다. 데몬 쓰레드는 garbage collection, 요청 처리, 리소스 청소와 같은 백그라운드 태스크를 실행하며 낮은 우선순위를 가지고 있다.

일반 쓰레드가 모두 종료되면 데몬 쓰레드는 강제적으로 자동종료되는데, 당연하다.🤷‍♂️ 데몬쓰레드가 일반 쓰레드의 보조역할을 수행하므로 일반 쓰레드가 모두 종료되고 나면 데몬 쓰레드의 존재의 의미가 없기 때문이다.

데몬쓰레드는 무한 루프와 조건문을 이용해서 무한히 대기하고 있다가 특정 조건이 만족되면 작업을 실행하고, 다시 대기하게 된다.

데몬쓰레드는 일반쓰레드의 작성방법과 실행방법이 같으며, 다만 쓰레드를 생성한 다음 실행하기 전에 setDaemon(true)를 호출하기만 하면 된다. 그리고 데몬 쓰레드가 생성한 쓰레드는 자동적으로 데몬 쓰레드가 된다는 것도 알아두자.

boolean isDaemon() : 쓰레드가 데몬쓰레드인지 확인하여 데몬쓰레드이면 true
void setDaemon(boolean on) : 쓰레드를 데몬 쓰레드로 또는 사용자 쓰레드로 변경.
                             매개변수 on의 값을 true로 지정하면 데몬 쓰레드가 된다.

👉 setDeamon 메소드는 반드시 start()를 호출하기 전에 실행되어야 한다.
호출 후 실행되면 IllegalTrheadStateException이 발생한다.

public class ThreadEx4 implements Runnable {
    static boolean autoSave = false;

    public static void main(String [] args) {
        Thread t = new  Thread(new ThreadEx4());
        //t.setDaemon(true);    //얘가 없으면 종료되지 않는다.
        t.start();

        for(int i = 1; i < 5; i++) {
            try{
                Thread.sleep(1000);
            } catch(InterruptedException e) {}
            System.out.println(i);

            if(i == 2) {
                autoSave = true;
            }
        }
        System.out.println("시스템 종료");
    }

    public void run() {
        while(true) {
            try {
                Thread.sleep(3000);
            } catch(InterruptedException e) {}
            if(autoSave) {
                autoSave();
            }
        }
    }

    public void autoSave() {
        System.out.println("작업파일이 자동저장되었습니다.");
    }
}

t.setDaemon(true) 사용

1
2
작업파일이 자동저장되었습니다.
3
4

t.setDaemon(true) 사용안함

1
2
작업파일이 자동저장되었습니다.
3
4
시스템 종료
작업파일이 자동저장되었습니다.
작업파일이 자동저장되었습니다.
작업파일이 자동저장되었습니다.

즉, 데몬 스레드를 사용하게 되면 일반 스레드(main)가 종료되었을 때 자동으로 데몬 스레드가 종료되기 때문에, 실행결과를 통해 데몬 스레드를 사용할 때와 사용하지 않을 때의 차이를 확인할 수 있다.

  • 실행중인 모든 호출스택 출력
//getAllStackTraces() 사용
Map map = getAllStackTraces();
Iterator it = map.keySet().iterator();

해당 메소드(getAllStackTraces)를 사용하면 실행 중 또는 대기상태를 호출할 수 있다.(작업이 완료되지 않은 모든 스레드의 호출 스택을 확인할 수 있다.)

프로그램을 실행하면 JVM은 가비지 컬렉션, 이벤트처리, 그래픽 처리 등 프로그램이 실행되는데 필요한 보조작업을 수행하는 데몬 스레드를 자동적으로 생성해서 실행시킨다.
얘네들(데몬 스레드)은 Sytem Thread Group 또는 Main Thread Group 에 속한다.


참고자료

0개의 댓글