Thread와 Thread Scheduler

Shin·2022년 12월 7일
0

Java

목록 보기
5/6

🔰 Thread 생성과 실행

Java에서 Thread 를 만들기 위해서는 Java가 제공하는 Thread class 를 이용한다.

방법에는 2가지가 존재한다.
1. Thread class를 상속받는 하위 클래스를 생성하여 사용하기.
2. 인터페이스를 구현하여 Thread 클래스로부터 직접 생성하여 사용하기.

1. Thread를 상속받는 하위 클래스 생성


class ThreadEx_01_1 extends Thread{
	
	@Override
	public void run() {
		 // Thread에서 실행하고자 하는 코드 
		}
	}	
}

Main Method에서 실행할 때에는 아래와 같다.

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

상속을 이용하는 경우의 단점
: 다중 상속의 사용이 불가능하기 때문에 제한이 생기게 된다.
클래스의 결합도가 높아지는 문제가 생긴다.



2. 인터페이스를 구현한 클래스 생성

인터페이스를 사용하는 경우가 조금 더 객체지향적인 방법이다.
이 방법의 경우, 재활용성을 높이고 결합도를 낮출 수 있다.

class ThreadEx_02 implements Runnable{

	@Override
	public void run() {
		// 쓰레드가 실행할 코드
	}	
}
Main Method에서 실행할 때에는 아래와 같다.

public class ThreadTest{
	public static void main(String[] args){
    	
        ThreadEx_01_2 r2 = new ThreadEx_01_2();
        Thread t2 = new Thread(r2);
        r2.start();
    }
}



🔰 Thread의 상태전이도

📌New

각 쓰레드 객체의 start() 메서드를 통해 동작시키게 되면 해당 쓰레드 객체는 JVM의 쓰레드 스케줄링 대상이 되며 Runnable 상태에 돌입하게 됩니다. 한 번 New 상태에 돌입한 쓰레드는 다시 New 상태가 될 수 없습니다.

📌Runnable

New 상태를 지나온 쓰레드는 Runnable 큐에 대기하게 되는데 JVM은 각 쓰레드의 우선순위에 따라서 Running 상태로 만들어 쓰레드를 동작시킵니다.

📌Running

쓰레드 스케줄러에 의해 Running 상태로 이동하게된 쓰레드는 재정의된 run() 메소드가 호출됩니다.

run() 에 의해 실행되며 run() 메소드가 종료되면 Terminate 상태가 됩니다. 이 때 I/O 호출, sleep(), join() 등의 인터럽트가 발생하게 되면 쓰레드는 Waiting Pool 로 이동하여 대기하게 됩니다.

또한 해당 쓰레드의 run() 메소드의 수행이 길어지거나 yield() 메서드가 수행되게되면 JVM은 해당 쓰레드를 다시 Runnable 상태로 옮깁니다.

📌Waiting

쓰레드의 수행 중 I/O 블로킹이나 sleep(), join() 의 메서드에 의해 대기해야할 경우 쓰레드는 Waiting Pool 로 이동하게 됩니다.
Waiting Pool 내의 쓰레드는 해당 I/O의 수행을 마치거나, sleep(), join() 등의 대기 조건이 끝나거나 혹은 인터럽트가 발생되게 되면 다시 Runnable 큐로 이동합니다.

📌Dead(Terminate)

Running 상태의 쓰레드가 run() 메소드의 수행이 끝나면 Terminate 상태가 되며 쓰레드가 종료됩니다. 한번 Terminate 된 쓰레드 객체는 다시 쓰레드 start() 메서드를 호출하여 스레드 스케쥴링에 포함시킬 수 없습니다.

Context Switching

shingle core에서 여러 개의 쓰레드가 한 번에 실행되게 되면 각 쓰레드들을 스위치하면서 작업을 실행하게 된다.
이 때, 각각의 쓰레드들을 바꿔가며 실행하는 과정에서 기존의 정보를 버리고 새로 실행할 쓰레드의 정보를 업데이트하는 과정이 필요로해진다. 이러면서 불필요한 오버헤드가 발생하는 것을 콘텍스트 스위칭이라고 부른다.



🔰 Thread의 우선순위

JVM은 runnable 상태의 Thread가 여러 개 존재할 때 어떤 Thread가 실행될 지 Thread Scheduler 가 처리하게 된다. 우선순위가 높을수록 Running(실행)할 수 있는 기회가 더 많아지게 된다.

다만 사용자가 Thread의 우선순위를 지정할 수 있다.
우선순위는 1부터 10까지 지정할 수 있으며, 기본값은 5이다.

Java에서는 getPriority(), setPriority() 라는 메소드를 이용해 우선순위를 지정할 수 있다.

** 다만 싱글코어가 아닌, 멀티코어 이상의 현 환경에서는 사용자가 우선순위를 효과적으로 지정해 사용하기는 어렵다.



🔰 Daemon Thread(데몬 쓰레드)

다른 쓰레드를 보조하는 쓰레드를 의미

일반 쓰레드가 종료되면, 보조 역할을 하던 Daemon Thread 역시 자동으로 종료된다.

대표적인 예로는 word나 excel과 같은 사무 프로그램에서의 자동 저장 기능

데몬 쓰레드는 쓰레드를 생성한 뒤에 setDaemon(true) 메소드를 이용해서 만들어줄 수 있다.



🔰 Thread의 실행제어 메소드


📌sleep()

지정된 시간만큼 Thread를 재우는 메소드
try~catch 구문을 통해 예외처리를 해주어야 한다.
static 메소드이기 때문에 sleep() 메소드를 호출한 쓰레드가 sleep에 들어가게 된다.
sleep에서 꺠어난 쓰레드는 다시 Runnable 상태로 되돌아가게 된다.

** 따라서 주의할 점은, sleep() 으로 3초의 대기를 주었다고 해서 sleep에서 꺠어난 쓰레드가 즉각적으로 실행되리라는 보장은 없다는 점이다. 왜냐하면 Runnable 에서 실행되는 쓰레드를 선택하는 것은 JVM 이 처리하는 것이기 때문이다.

📌join()

join 메소드는 join()을 호출한 쓰레드가 종료될 때까지 기다리게 한다. 호출한 쓰레드를 Running -> Waiting 상태로 보내는 것이다.
(매개변수로 어느정도 기다릴지 시간을 지정할 수도 있고, 지정하지 않는다면 해당 쓰레드가 끝날 때까지 기다리게 된다.)

public static void main(String[] args){
	Thread t = new Thread();
    t.join(100); // 0.1초만큼 메인 쓰레드를 멈추고 t라는 쓰레드를 실행시키겠다.
}

📌yield()

yield()는 쓰레드 자신에게 주언 실행시간을 다음 차례의 쓰레드에게 양보(yield) 한다. 예를들어, 스케쥴러에 의해 1초의 실행시간을 할당받은 쓰레드가 0.5초의 시간동안 작업한 상태에서 yield()가 호출되면 나머지 0.5초는 포기하고 다시 실행대기 상태가 된다.

📌interrupt()

interrupt() 메소드는 sleep(), join(), wait()에 의해 일시정지 상태인 쓰레드를 즉각적으로 깨워서(sleep의 매개변수랑은 상관없이) 실행 대기 상태로 만듦과 동시에 try~catch 구문으로 예외처리해놓은 InterruptedException가 발생된다.

하지만 sleep(), join(), wait() 과 같은 일시정지 상태에서 interrupt()로 실행대기 상태로 바뀌는 순간 해당 쓰레드의 interrupt의 상태값을 true에서 false로 초기화시킨다. 즉 interrupt() 실행 전과 동일한 값이 되게 된다.

참조
https://slidesplayer.org/slide/17001029/

0개의 댓글

관련 채용 정보