멀티스레드 (1)

띠로리·2024년 3월 22일

운영체제는 실행 중인 프로그램을 프로세스(process)로 관리한다. 멀티 태스킹(multi tasking)은 두 가지 이상의 작업을 동시에 처리하는 것을 말하는데, 이때 운영체제는 멀티 프로세스를 생성해서 처리한다.
하지만, 멀티 태스킹이 꼭 멀티 프로세스를 뜻하지는 않는다.

하나의 프로세스가 멀티 태스킹을 할 수 있는 이유?

하나의 프로세스 내에서 멀티 태스킹을 할 수 있도록 만들어진 프로그램들도 있다.
➡ 메신저는 채팅 작업을 하면서 동시에 파일 전송 작업을 수행하기도 한다.
이유는 바로 멀티 스레드(multi thread)가 있기 때문이다.
멀티 프로세스가 프로그램 단위의 멀티 태스킹이라면 멀티 스레드는 프로그램 내부에서의 멀티 태스킹이라고 볼 수 있다.

멀티 프로세스들은 서로 독립적이기 때문에 하나의 프로세스에서 오류가 발생해도 다른 프로세스에게 영향을 미치지 않는다. 하지만 멀티 스레드는 프로세스 내부에서 생성되기 때문에 하나의 스레드가 예외를 발생시키면 프로세스가 종료되므로 다른 스레드에게 영향을 미친다.

메인 스레드

모든 자바 프로그램은 메인 스레드(main thread)가 main() 메소드를 실행하면서 시작된다.

public static void main(String[] args) {
	String data = nul;
    if (...) {
    }
    while (...) {
    }
    System.out.println("...");

메인 스레드는 main() 메소드의 첫 코드부터 순차적으로 실행하고, main() 메소드의 마지막 코드를 실행하거나 return 문을 만나면 실행을 종료한다.

싱글 스레드에서는 메인 스레드가 종료되면 프로세스도 종료된다. 하지만 멀티 스레드에서는 실행 중인 스레드가 하나라도 있다면 프로세스는 종료되지 않는다. 메인 스레드가 작업 스레드보다 먼저 종료되더라도, 작업 스레드가 계속 실행 중이라면 프로세스는 종료되지 않는다.


작업 스레드의 생성과 실행

멀티 스레드로 실행하는 프로그램을 개발하려면 먼저 몇 개의 작업을 병렬로 실행할지 결정하고 각 작업별로 스레드를 생성해야 한다.

자바 프로그램은 메인 스레드가 반드시 존재하기 때문에 메인 작업 이외에 추가적인 작업 수만큼 스레드를 생성하면 된다.
자바는 작업 스레드도 "객체"로 관리하므로 클래스가 필요하다.

Thread 클래스로 직접 생성

Thread thread = new Therad(Runnable Target);

java.lang 패키지에 있는 Thread 클래스로부터 작업 스레드 객체를 직접 생성하는 방법이다.
Runnable은 스레드가 작업을 실행할 때 사용하는 인터페이스로, run() 메소드가 정의되어 있다. 구현 클래스는 run()을 재정의해서 스레드가 실행할 코드를 가지고 있어야 한다.

class Task implements Runnable {
	@Override
    public void run() {
    	// 스레드가 실행할 코드
    }
}

명시적인 Runnable 구현 클래스를 작성하지 않고 Thread 생성자를 호출할 때 Runnable 익명 구현 객체를 매개값으로 사용할 수 있다. 이 방법이 가장 자주 사용된다.

Thread thread = new Thread(new Runnable() {
	@Override
    public void run() {
    	// 스레드가 실행할 코드
    }
}

작업 스레드 객체가 생성되었다고 해서 바로 작업 스레드가 실행되지는 않는다. 작업 스레드를 실행하려면 thread.start() 로 스레드 객체의 start() 메소드를 호출해야 한다.

Thread 자식 클래스로 생성

작업 스레드 객체를 생성하는 또 다른 방법은 Thread의 자식 객체로 만드는 것이다.

public class WorkerThread extends Thread {
	@Override
    public void run() {
    	// 스레드가 실행할 코드
    }
}

Thread thread = new WorkerThread(); // 스레드 객체 생성

마찬가지로, 명시적은 자식 클래스를 정의하지 않고 익명 자식 객체를 사용할 수도 있다. 이 방법이 더 많이 사용된다.

Thread thread = new Thread() {
	@Override
 	public void run() {
    	// 스레드가 실행할 코드
    }
};
thread.start();

스레드 이름

스레드는 자신의 이름을 가지고 있다!
메인 스레드의 이름은 main이고, 작업 스레드는 자동적으로 'Thread-n'이라는 이름을 가진다.
만약 작업 스레드의 이름을 다른 이름으로 설정하고 싶다면?
thread.setName("스레드 이름"); 을 사용해 바꿀 수 있다.

Thread thread = Thread.currentThread();
System.out.println(thread.getName());

스레드 이름은 디버깅할 때 어떤 스레드가 작업을 하는지 조사할 목적으로 주로 사용된다. 현재 코드를 어떤 스레드가 실행하고 있는지 확인하려면 정적 메소드인 currentThread()로 스레드 객체의 참조를 얻은 다음, getName() 메소드로 이름을 출력해보면 된다.


스레드 상태

스레드 객체를 생성(NEW)하고, start() 메소드를 호출하면 곧바로 스레드가 실행되는 것이 아니라 실행 대기 상태(RUNNABLE)가 된다.
실행을 대기하는 스레드는 CPU 스케쥴링에 따라 CPU를 점유하고 run() 메소드를 실행한다. 이때를 실행(RUNNING) 상태라고 한다.
실행 상태에서 run() 메소드가 종료되면 더 이상 실행할 코드가 없기 때문에 스레드의 실행은 멈추게 된다. 이 상태를 종료 상태(TERMINATED)라고 한다.

이미지 출처 https://math-coding.tistory.com/173

주어진 시간 동안 일시 정지

sleep()
실행 중인 스레드를 일정 시간 멈추게 하고 싶다면 Thread 클래스의 정적 메소드인 sleep()을 이용하면 된다. 매개값에는 얼마 동안 일시 정지 상태로 있을 것인지 밀리세컨드(1/1000) 단위로 시간을 주면 된다.

일시 정지 상태에서는 InterruptedException이 발생할 수 있기 때문에 sleep()은 예외 처리가 필요한 메소드이다.

다른 스레드의 종료를 기다림

join()
다른 스레드가 종료될 때까지 기다렸다가 실행을 해야 하는 경우, 예를 들어 계산 스레드의 작업이 종료된 후 그 결과값을 받아 처리하는 경우에는 join() 메소드를 사용한다.

profile
차곡 차곡 기록 쌓기

0개의 댓글