Java 초보 개발자를 위한 멀티스레딩과 동시성
KUKJIN LEE • 3주 전 작성
Java에서 멀티스레딩과 동시성은 Spring Boot와 같은 프레임워크를 사용할 때 중요한 역할을 합니다. 초보 개발자가 멀티스레딩을 이해하고 Spring Boot 개발에 적용하기 위해 반드시 알아야 할 필수 개념입니다.
1. 스레드(Thread)란?
-
정의: 스레드는 프로그램 내에서 독립적으로 실행될 수 있는 단위입니다. 기본적으로 모든 Java 프로그램은 메인 스레드를 포함하고 있으며, 개발자는 추가적인 스레드를 생성하여 병렬 처리를 할 수 있습니다.
2. 스레드 생성 방법
Java에서 스레드를 생성하는 기본적인 두 가지 방법은 다음과 같습니다.
-
클래스 상속 (
extends
)
class MyThread extends Thread {
public void run() {
System.out.println("Thread is running");
}
}
public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // 스레드 시작
}
}
Thread
클래스를 상속받는 방식은 클래스가 이미 Thread
를 상속받고 있기 때문에 다른 클래스를 상속할 수 없습니다. 이는 상속에 제한이 있습니다.
인터페이스 구현 (implements
)
interface Task1 {
void task1();
}
interface Task2 {
void task2();
}
class MyRunnable implements Runnable, Task1, Task2 {
public void run() {
System.out.println("Runnable is running");
}
public void task1() {
System.out.println("Task 1 is executed");
}
public void task2() {
System.out.println("Task 2 is executed");
}
}
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
t1.start(); // 스레드 시작
// Task1과 Task2의 메서드도 호출 가능
myRunnable.task1();
myRunnable.task2();
}
}
인터페이스를 구현하는 방식은 클래스가 다른 클래스를 상속받을 수 있으므로 더 큰 확장성을 가집니다. 인터페이스는 다중 구현이 가능하기 때문에 여러 기능을 동시에 구현할 수 있는 장점이 있습니다.
비교 요약:
-
extends:
Thread
클래스를 상속받아 사용, 다른 클래스를 상속할 수 없음. -
implements:
Runnable
인터페이스를 구현하여 사용, 다른 클래스를 상속할 수 있으며 다중 인터페이스도 구현 가능.
3. 동기화(Synchronization)
-
정의: 여러 스레드가 동시에 공유 자원에 접근할 때, 데이터의 일관성을 유지하기 위해 동기화가 필요합니다.
-
synchronized
키워드: 특정 메서드나 블록을 동기화하여 한 번에 하나의 스레드만 접근할 수 있게 만듭니다.
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
사용 이유: 동기화를 통해 여러 스레드가 동시에 한 객체에 접근하여 발생할 수 있는 데이터 불일치 문제를 해결할 수 있습니다.
4. 스레드 상태(Thread States)
Java 스레드는 실행 중 다양한 상태를 가질 수 있습니다. 이를 이해하면 스레드의 실행 흐름을 관리하는 데 도움이 됩니다.
-
NEW: 스레드가 생성되었지만 아직 시작되지 않은 상태.
-
RUNNABLE: 스레드가 실행 중이거나 실행 준비 상태.
-
BLOCKED: 스레드가 락을 기다리며 일시 중단된 상태.
-
WAITING: 다른 스레드가 특정 작업을 완료하기를 기다리는 상태.
-
TERMINATED: 스레드 실행이 완료된 상태.
이러한 상태는 스레드 실행 중 발생할 수 있는 문제를 디버깅할 때 유용합니다.
class MyThread extends Thread {
public void run() {
try {
// 스레드 상태를 RUNNABLE 상태로 만든 후 일시 중지
Thread.sleep(100); // TIMED_WAITING 상태
synchronized (this) {
wait(); // WAITING 상태
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
// NEW 상태: 스레드를 생성했지만 시작하지 않은 상태
System.out.println("Thread state after creation: " + t1.getState());
t1.start();
// RUNNABLE 상태: 스레드가 실행 중이거나 실행 준비 상태
System.out.println("Thread state after start: " + t1.getState());
// 잠시 대기하여 스레드가 sleep에 진입할 시간을 줌
Thread.sleep(50);
System.out.println("Thread state during sleep (TIMED_WAITING): " + t1.getState());
// 스레드가 wait에 진입할 시간을 줌
Thread.sleep(150);
System.out.println("Thread state during wait (WAITING): " + t1.getState());
// 스레드가 WAITING 상태일 때 notify 호출
synchronized (t1) {
t1.notify(); // WAITING 상태에서 깨워줌
}
// 스레드가 종료될 때까지 기다림
t1.join();
// TERMINATED 상태: 스레드 실행이 완료된 상태
System.out.println("Thread state after termination: " + t1.getState());
}
}
5. extends
와 implements
의 의미
-
extends
(확장하다): 클래스가 다른 클래스를 확장하여 그 기능을 물려받습니다. 상속 구조에서 부모 클래스의 기능을 자식 클래스에서 사용할 수 있게 됩니다. -
implements
(구현하다): 클래스가 인터페이스를 구현하여 인터페이스에 정의된 메서드들을 실제로 구현합니다. 여러 인터페이스를 동시에 구현할 수 있어 다중 기능을 제공할 수 있습니다.
-
예시:
-
extends
: 기존 클래스를 확장하여 상속받는 경우. -
implements
: 인터페이스를 구현하여 새로운 기능을 클래스에 추가하는 경우.
-