CodeGym /행동 /JAVA 25 SELF /스레드의 매개변수와 우선순위

스레드의 매개변수와 우선순위

JAVA 25 SELF
레벨 51 , 레슨 3
사용 가능

1. 스레드에 매개변수 전달

스레드를 시작할 때는 파일 이름, 숫자 범위, 인사말 같은 ‘작업’을 넘겨주고 싶을 때가 많습니다. 매개변수가 없는 스레드는 주소 없는 배달원과 같습니다: 도시를 뛰어다니지만 피자를 어디에 배달해야 할지 모릅니다.

어떻게 하나요?

Java에서 스레드는 보통 두 가지 방식으로 생성됩니다:

  • Thread 클래스를 상속
  • Runnable 인터페이스 구현(또는 람다식 사용)

매개변수 전달은 대개 Runnable을 구현하는 클래스의 생성자를 통해 이뤄집니다. 간단하고, 안전하며, 명확합니다. 필드를 final로 두면 다른 스레드에서 매개변수가 바뀌지 않도록 보호할 수 있습니다. 세터/공개 필드를 사용하면 동기화 문제를 일으키기 쉽습니다.

예제 1: 생성자를 통한 매개변수 전달 스레드

학습용 애플리케이션을 보강해 보겠습니다: 사용자 주문을 별도 스레드에서 처리합니다.

public class OrderProcessor implements Runnable {
    private final String orderId;

    public OrderProcessor(String orderId) {
        this.orderId = orderId;
    }

    @Override
    public void run() {
        System.out.println("주문 처리 중: " + orderId + " 스레드 " + Thread.currentThread().getName());
        // 여기에는 오래 걸리는 처리가 있을 수 있습니다...
    }
}

스레드를 생성하고 시작합니다:

public class Main {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new OrderProcessor("ORDER-001"));
        Thread thread2 = new Thread(new OrderProcessor("ORDER-002"));
        thread1.start();
        thread2.start();
    }
}

예상 출력:

주문 처리 중: ORDER-001 스레드 Thread-0
주문 처리 중: ORDER-002 스레드 Thread-1

예제 2: 람다식(Java 8+)으로 매개변수 전달

작업이 단순하다면 별도의 클래스를 만들지 않아도 됩니다.

public class Main {
    public static void main(String[] args) {
        String user1 = "Vasya";
        String user2 = "Katya";

        Thread thread1 = new Thread(() -> {
            System.out.println("안녕, " + user1 + " 에서 " + Thread.currentThread().getName());
        });

        Thread thread2 = new Thread(() -> {
            System.out.println("안녕, " + user2 + " 에서 " + Thread.currentThread().getName());
        });

        thread1.start();
        thread2.start();
    }
}

왜 세터/게터로 매개변수를 전달하면 안 될까요?
세터나 공개 필드로 매개변수를 전달하면, 실행 중에 다른 스레드가 값을 바꿔버릴 위험이 있습니다. 이는 잡기 어려운 버그로 이어질 수 있습니다. 필드를 final로 만들고 생성자를 통해 전달하는 것이 좋습니다.

2. 우선순위

Java에서 각 스레드는 우선순위를 갖습니다 — 1부터 10까지의 정수로, 스케줄러(scheduler)에게 다른 스레드와 비교해 이 스레드가 얼마나 ‘중요한지’를 알립니다. 기본적으로 모든 스레드는 우선순위 5(Thread.NORM_PRIORITY)를 가집니다.

중요: 우선순위는 운영체제에 주는 ‘힌트’일 뿐입니다. 우선순위가 10이라고 해서 1보다 항상 더 빨리 실행된다는 보장은 없습니다. 운영체제, 그 설정, 그리고 현재 부하에 따라 달라집니다.

스레드의 우선순위를 어떻게 설정하나요?

Thread 클래스에는 다음 메서드가 있습니다:

  • setPriority(int newPriority)
  • getPriority()

표준 상수 세 가지:

  • Thread.MIN_PRIORITY (1)
  • Thread.NORM_PRIORITY (5)
  • Thread.MAX_PRIORITY (10)

예제 3: 스레드 우선순위 설정

public class PriorityDemo {
    public static void main(String[] args) {
        Runnable task = () -> {
            System.out.println("스레드 " + Thread.currentThread().getName() +
                    " — 우선순위 " + Thread.currentThread().getPriority());
        };

        Thread low = new Thread(task, "LowPriority");
        Thread norm = new Thread(task, "NormalPriority");
        Thread high = new Thread(task, "HighPriority");

        low.setPriority(Thread.MIN_PRIORITY);    // 1
        norm.setPriority(Thread.NORM_PRIORITY);  // 5
        high.setPriority(Thread.MAX_PRIORITY);   // 10

        low.start();
        norm.start();
        high.start();
    }
}

예상 출력(행 순서는 보장되지 않습니다!):

스레드 LowPriority — 우선순위 1
스레드 HighPriority — 우선순위 10
스레드 NormalPriority — 우선순위 5

우선순위가 실행 순서에 영향을 줄까요?

대부분의 경우 — 아닙니다. 우선순위는 스레드에 할당되는 CPU 시간에 영향을 줄 수는 있지만, 시작/종료 순서를 보장하지는 않습니다. 프로그램의 논리를 우선순위에 기대지 마세요 — 예를 들어 백그라운드 스레드가 메인 스레드를 방해하지 않도록 하는 등 ‘부드러운’ 힌트로만 사용하세요.

표: 스레드 우선순위 상수

상수 설명
Thread.MIN_PRIORITY
1 가장 낮은 우선순위
Thread.NORM_PRIORITY
5 보통 우선순위
Thread.MAX_PRIORITY
10 가장 높은 우선순위

3. 스레드 이름 지정

스레드 이름은 다중 작업 세계의 ‘별명’입니다. 스레드가 수십 개일 때, 로그에 "Thread-7" 대신 "FileUploader-1"이 보이면 디버깅이 훨씬 쉬워집니다. 특히 로그 분석과 오류 추적에 중요합니다.

스레드 이름을 설정하는 방법?

이름은 Thread 생성자에서 바로 지정하거나 setName 메서드로 설정할 수 있습니다. 현재 이름을 알 수 있는 메서드는 getName입니다.

Thread t = new Thread(() -> {
    System.out.println("작동 중!");
}, "MyThreadName");
t.start();

예제 4: 이름 있는 스레드

public class NamedThreads {
    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            System.out.println("나는 스레드: " + Thread.currentThread().getName());
        }, "Downloader");

        Thread threadB = new Thread(() -> {
            System.out.println("나는 스레드: " + Thread.currentThread().getName());
        });
        threadB.setName("Uploader");

        threadA.start();
        threadB.start();
    }
}

예상 출력:

나는 스레드: Downloader
나는 스레드: Uploader

4. 실습: 서로 다른 매개변수, 우선순위, 이름을 가진 여러 스레드

모두를 하나의 예제로 합쳐 보겠습니다: 각 주문은 고유한 이름과 우선순위를 가진 별도 스레드입니다.

public class OrderProcessor implements Runnable {
    private final String orderId;
    private final int processingTimeMs;

    public OrderProcessor(String orderId, int processingTimeMs) {
        this.orderId = orderId;
        this.processingTimeMs = processingTimeMs;
    }

    @Override
    public void run() {
        System.out.println("[" + Thread.currentThread().getName() + "] 주문 처리 시작: " + orderId +
                " (우선순위 " + Thread.currentThread().getPriority() + ")");
        try {
            Thread.sleep(processingTimeMs); // 작업 모사
        } catch (InterruptedException e) {
            System.out.println("[" + Thread.currentThread().getName() + "] 중단됨!");
        }
        System.out.println("[" + Thread.currentThread().getName() + "] 주문 처리 완료: " + orderId);
    }

    public static void main(String[] args) {
        Thread fastOrder = new Thread(new OrderProcessor("FAST-ORDER", 500), "FastOrderThread");
        Thread normalOrder = new Thread(new OrderProcessor("NORMAL-ORDER", 1000), "NormalOrderThread");
        Thread slowOrder = new Thread(new OrderProcessor("SLOW-ORDER", 2000), "SlowOrderThread");

        fastOrder.setPriority(Thread.MAX_PRIORITY);
        normalOrder.setPriority(Thread.NORM_PRIORITY);
        slowOrder.setPriority(Thread.MIN_PRIORITY);

        fastOrder.start();
        normalOrder.start();
        slowOrder.start();
    }
}

무엇을 보게 될까요:

  • 각 스레드는 어떤 주문을 처리하는지, 자신의 이름과 우선순위를 출력합니다.
  • Thread.sleep으로 처리 시간을 모사합니다.
  • 스레드 종료 순서는 우선순위와 일치하지 않을 수 있습니다.

5. 중요한 뉘앙스와 특징

매개변수 전달: 생성자만 사용하세요!

그게 더 안전합니다. 매개변수를 final로 선언하면, 객체 생성 후 변경할 수 없고 다른 스레드가 값을 ‘끼워 넣을’ 수 없습니다. 데이터 레이스를 예방하는 데 중요합니다.

우선순위: 비즈니스 로직을 기대하지 마세요

우선순위는 종업원에게 “커피 조금 빨리 부탁해요”라고 말하는 것과 같습니다. 가끔은 통하지만, 가끔은 아닙니다. 운영체제를 위한 권고일 뿐, 실행 규칙이 아닙니다.

스레드 이름: 디버깅에 적극 활용하세요

로그 분석 시 시간을 크게 절약해 줍니다. 처음부터 의미 있는 이름을 붙이는 습관을 들이세요.

6. 스레드 매개변수와 우선순위에서 흔한 실수

오류 #1: 공개 필드로 매개변수를 전달. 매개변수를 일반 필드로 선언하고 스레드 시작 후 변경하면 예측 불가능한 결과를 초래할 수 있습니다. final 필드와 생성자를 사용하세요.

오류 #2: 우선순위가 순서를 보장한다고 기대. Thread.MAX_PRIORITY를 설정했다고 해서 항상 먼저 실행된다고 생각하나요? 아닙니다. 동기화나 로직 제어에 우선순위를 사용하지 마세요.

오류 #3: 로그에 이름 없는 스레드. 로그에 “Thread-3”, “Thread-7”만 있으면 원인을 찾기 어렵습니다. 생성자나 setName으로 스레드에 의미 있는 이름을 부여하세요.

오류 #4: 하나의 Runnable 객체를 여러 스레드에 사용. Runnable 객체가 상태를 가지고 있고 이를 여러 스레드에 전달하면 매개변수를 ‘공유’하게 되어 데이터 레이스로 직행합니다. 스레드마다 별도의 Runnable을 생성하세요.

오류 #5: set 메서드로 매개변수 전달. 객체 생성/시작 후 set* 메서드로 매개변수를 지정하면 실행 중 값이 바뀔 수 있습니다 — 전형적인 race condition입니다.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION