☕️ [Java] 비동기 처리: Thread 클래스와 Runnable 인터페이스

이서·2023년 9월 25일
1

☕️ Java 트랙

목록 보기
2/3

🏎️💨 안녕하세요 이서에요. 이번 포스팅에서는 비동기 처리의 기본이 되는 자바의 Thread와 Runnable에 대해서 포스팅하고자 해요.

개요

ThreadRunnable은 자바 프로그래밍에서 다중 스레드 환경을 다룰 때 중요한 개념이에요. 이 두 가지 요소는 병렬 처리 및 동시성 작업을 관리하고 제어하는 데 사용해요. 아래는 Thread와 Runnable에 대해 간략하게 정리한 내용이에요.

  1. Runnable:
    • 정의: Runnable은 자바에서 다중 스레드를 구현하기 위한 인터페이스에요. Runnable 인터페이스를 구현한 객체는 실행 가능한 코드를 나타내며, 스레드에 의해 실행될 수 있어요.
    • 사용: Runnable을 구현한 객체는 스레드 생성 시에 생성자에 전달하거나 Thread 클래스의 run 메서드를 오버라이드하여 실행 가능한 코드를 정의할 수 있어요.
    • 장점: Runnable을 사용하면 스레드 클래스와 작업 클래스를 분리할 수 있으며, 코드 재사용성과 유연성을 향상시킬 수 있어요
  2. Thread:
    • 정의: 스레드는 프로세스 내에서 실행되는 작은 실행 단위로, 독립적으로 실행될 수 있는 코드의 실행 흐름입니다. 하나의 프로세스에는 여러 개의 스레드가 있을 수 있으며, 각 스레드는 동시에 실행돼요.
    • 스레드 생성: 자바에서는 java.lang.Thread 클래스를 사용하여 스레드를 생성해요. 스레드를 만들 때는 일반적으로 스레드가 실행할 코드를 Runnable 객체로 전달해요.
    • 스레드 우선순위: 각 스레드는 우선순위를 가지며, 높은 우선순위를 갖는 스레드는 낮은 우선순위를 갖는 스레드보다 CPU 자원을 더 많이 할당받을 수 있어요.
    • 동기화와 스레드 안전성: 다중 스레드 환경에서는 여러 스레드가 공유 자원에 동시에 접근할 수 있으므로, 동기화를 통해 데이터 무결성을 유지하고 스레드 안전성을 보장해야해요.

Runnable

Runnable 인터페이스는 자바에서 다중 스레드를 사용하여 병렬 처리를 구현하기 위한 핵심적인 인터페이스 중 하나에요. Thread를 실행하기 위해서는 Runnable 인터페이스를 구현한 인스턴스가 필요해요. Runnable 인터페이스는 매개변수가 없는 run() 메서드를 구현하도록 되어 있어요. 이 메서드에는 스레드가 실행될 때 수행할 코드를 정의해요.

@FunctionalInterface
public interface Runnable {
	public abstarct void run();
}

Thread

Thread 클래스는 우리의 프로그램 내에서 단일 Thread를 실행시킬 수 있어요. JVM(Java Virtual Machine)은 하나의 어플리케이션에서 동시에 다중 스레드를 실행할 수 있기 때문에, 여러 Thread 인스턴스를 실행시킬 수 있어요.

Thread를 사용하기 위해서는 기본적으로 두 가지 방법이 있어요. 첫 번째로는 Thread 클래스를 상속받아 사용하는 것이고, 두 번째로는 Runnable 인터페이스를 Thread 생성자의 매개변수로 넣어주는 방법이에요. 우선 Thread 클래스를 상속받아 사용하는 방법에 대해서 알아볼게요.

방법1. Thread 클래스를 상속받아 구현

Thread는 Runnable 인터페이스를 구현하고 있어요. 따라서 run() 메서드를 오버라이드해서 사용할 수 있어요.

public class MyThread extends Thread {
	@Override
	public void run() {
		System.out.println("MyThread is running");
	}
}

Thread는 start() 메서드를 통해 우리가 오버라이드한 run() 메서드를 실행할 수 있어요.

public static void main(String[] args) {
	Thread myThread = new MyThread();
	myThread.start(); // MyThread is running
}

방법2. Runnable 인터페이스를 구현하여 인자로 전달

우선 우리의 클래스에서 Runnable 인터페이스를 구현해요.

public class MyRunnableImpl implements Runnable {
	@Override
	public void run() {
		System.out.println("MyRunnableImpl is running");
	}
}

Thread를 인스턴스화 할 때 생성자의 매개변수로 Runnable 인스턴스를 주입해요.

public static void main(String[] args) {
	Runnable myRunnable = new MyRunnableImpl();
	Thread myThread= new Thread(myRunnable);
	myThread.start(); // MyRunnableImpl is running
}

방법3. 익명 클래스로 Runnable 인터페이스를 구현하여 인자 전달

Runnable 인터페이스를 구현하지 않고, 익명 클래스로 구현할 수 있어요.

public static void main(String[] args) {
	Thread myThread= new Thread(new Runnable() {
		@Override
		public void run() {
			System.out.println("MyRunnableImpl is running");
		}
	});
	myThread.start(); // MyRunnableImpl is running
}

방법4. 람다(lambda)로 Runnable 인터페이스를 구현하여 인자로 전달

Runnable 인터페이스는 함수형 인터페이스(Functional Interface)이므로 람다(lambda)를 통해 구현할 수도 있어요.

public static void main(String[] args) {
	Thread myThread= new Thread(() -> {
		@Override
		public void run() {
			System.out.println("MyRunnableImpl is running");
		}
	});
	myThread.start(); // MyRunnableImpl is running
}

Priority (우선순위)

모든 Thread는 priority(우선순위)를 가지며, priority가 높을 수록 우선 순위가 낮은 Thread 보다 더 많은 리소스를 사용하기 위해 시도하며, 상대적으로 낮은 우선순위를 가진 스레드는 CPU 자원을 적게 얻으려고 시도해요.

priority의 기본값은 부모 Thread의 priority이며, 1부터 10까지의 priority를 가질 수 있어요. main Thread의 경우 기본적으로 5의 priority를 가지고 있어 main Thread에서 priority를 설정하지 않고 Thread를 생성한다면 기본적으로 5의 priority를 가져요. priority는 setPriority() 메서드를 통해 설정할 수 있어요.

import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;

public class ThreadTest {
	@Test // 테스트 통과
	public void mainThreadPriorityIs5() {
		// main Thread의 priority 테스트
		assertThat(Thread.currentThread().getPriority()).isEqualTo(5);
	}

	@Test // 테스트 통과
	public void defaultPriorityTest() {
		// 부모 Thread를 7로 설정한 이후 자식 Thread의 priority 확인 테스트
		Thread parentThread = new Thread(() -> {
			Thread childThread = new Thread();
			// 부모 Thread의 우선순위를 받아 7로 설정돼요.
			assertThat(childThread.getPriority()).isEqualTo(7);
		});
		parentThread.setPriority(7); // 부모 우선순위를 7로 변경했어요.
		parentThread.start();
	}
}

Thread 클래스에서는 상수로 priority 값들을 제공하고 있어요. 필요에 따라 활용할 수 있어요.

/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;

/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;

/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;

만약 priority가 1이하 이거나 10을 초과한다면 IllegalArgumentException이 발생해요. 아래의 테스트 코드는 최소 priority인 1 이하이고, 최대 priority인 10을 초과하여 IllegalArgumentException이 발생되어 테스트 코드를 통과해요.

import org.junit.Test
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;

public class ThreadTest {
	@Test
	public void priorityTest() {
		Thread thread = new Thread();
		assertThatIllegalArgumentException().isThrownBy(() -> thread.setPriority(-1));
		assertThatIllegalArgumentException().isThrownBy(() -> thread.setPriority(11));
	}
}

스레드 우선순위는 다중 스레드 애플리케이션에서 특정 작업에 중요도를 할당하거나 특정 스레드를 특정 작업에 사용하기 위해 제어하는 데 사용될 수 있지만, 운영체제 및 하드웨어의 동작에 따라 실제로는 다소 예측하기 어려울 수 있어요. 스레드의 우선순위를 사용할 때는 주의가 필요하며, 대부분의 애플리케이션에서는 기본값인 중간 우선순위로 충분해요.

마무리

Thread와 Runnable은 자바에서 다중 스레드 프로그래밍을 효과적으로 다루기 위한 핵심 개념이에요. 스레드는 병렬 처리를 가능하게 하며 Runnable을 사용하면 실행 가능한 작업을 쉽게 정의하고 스레드에게 전달할 수 있어요. 이를 통해 자바 애플리케이션에서 동시성을 구현하고 효율적으로 다양한 작업을 처리할 수 있어요.

참고자료

Runnable (Java Platform SE 8 )
Thread (Java Platform SE 8 )
profile
🏎️💨 Beep Beep

0개의 댓글