새발블로그
[JAVA] 멀티스레드 본문
1. 스레드(Thread)란?
- 스레드는 프로그램 내에서 독립적으로 실행되는 흐름의 단위.
- 하나의 프로세스는 최소한 하나 이상의 스레드를 가질 수 있다.
- 프로세스: 프로그램이 메모리에서 실행되는 단위
- 스레드: 프로세스 내에서 실행되는 작업의 흐름
2. 스레드 생성 방법
1) Thread 클래스 상속
Thread 클래스를 상속받아 run() 메서드를 오버라이드하고, start() 메서드를 호출하여 스레드를 시작한다.
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 스레드 시작
}
}
2) Runnable 인터페이스 구현
Runnable 인터페이스를 구현하여 run() 메서드를 정의한 후, 이를 Thread 객체에 전달하여 실행할 수 있다.
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable is running");
}
}
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start(); // 스레드 시작
}
}
3) 익명 클래스 사용
Runnable 인터페이스나 Thread 클래스를 익명 클래스 형태로 사용할 수 있다.
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread is running using anonymous class");
}
});
thread.start();
}
}
3. 스레드 동기화(Synchronization)
여러 스레드가 공유 자원을 동시에 접근할 때 문제가 발생할 수 있다. 이를 해결하기 위해 동기화를 사용하여 한 번에 하나의 스레드만 자원에 접근할 수 있도록 한다.
1) synchronized 키워드 사용
sychronized 키워드를 사용하면 메서드나 블록을 동기화하여, 한 번에 하나의 스레드만 해당 코드에 접근하도록 할 수 있다.
class SharedResource {
private int counter = 0;
// 동기화된 메서드
synchronized void increment() {
counter++;
}
synchronized int getCounter() {
return counter;
}
}
2) 동기화된 블록
특정 코드만 동기화하려면 메서드 대신 동기화된 블록을 사용할 수 있다.
class SharedResource {
private int counter = 0;
void increment() {
synchronized (this) { // 동기화된 블록
counter++;
}
}
int getCounter() {
return counter;
}
}
4. 임계영역(Critical Section)과 경쟁 상태(Race Condition)
여러 스레드가 동시에 공유 자원에 접근할 때 경쟁 상태가 발생할 수 있다. 이를 방지하기 위해 임계영역을 보호해야 한다.
경쟁 상태 예시
class Counter {
private int value = 0;
// 경쟁 상태가 발생할 수 있음
void increment() {
value++;
}
int getValue() {
return value;
}
}
해결법: synchronized 사용
경쟁 상태를 방지하기 위해 increment() 메서드를 동기화한다.
class Counter {
private int value = 0;
synchronized void increment() {
value++;
}
int getValue() {
return value;
}
}
5. 스레드 상태
- NEW: 스레드가 생성되었지만, 아직 start() 메서드가 호출되지 않은 상태.
- RUNNABLE: start() 메서드가 호출되어 실행 가능한 상태.
- BLOCKED: 다른 스레드가 자원에 접근하고 있어 대기 중인 상태.
- WAITING: wait() 메서드 등으로 다른 스레드가 자원을 반환할 때까지 대기하는 상태.
- TIMED_WAITING: 지정된 시간 동안 대기하는 상태.
- TERMINATED: 스레드의 실행이 종료된 상태.
6. 스레드 통신 (wait(), notify(), notifyAll())
스레드 간의 통신을 통해 서로 상태를 공유하거나, 대기와 알림을 관리할 수 있다.
1) wait() 메서드
wait() 메서드는 현재 스레드를 일시 정지시키고, 다른 스레드가 notify() 또는 notifyAll()을 호출할 때까지 대기한다.
class SharedResource {
private int counter = 0;
synchronized void increment() {
while (counter == 5) {
try {
wait(); // 조건이 맞지 않으면 대기
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
counter++;
notify(); // 다른 스레드에게 알림
}
synchronized int getCounter() {
return counter;
}
}
2) notify() / notifyAll() 메서드
notify()는 대기 중인 스레드 중 하나를 깨우고, notifyAll()은 모든 대기 중인 스레드를 깨운다.
class SharedResource {
private int counter = 0;
synchronized void increment() {
while (counter == 5) {
try {
wait(); // 대기
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
counter++;
notify(); // 대기 중인 스레드 하나를 깨운다
}
synchronized void reset() {
counter = 0;
notifyAll(); // 모든 대기 중인 스레드를 깨운다
}
synchronized int getCounter() {
return counter;
}
}
7. 스레드 풀 (ExecutorService)
스레드 풀을 사용하면 스레드를 효율적으로 관리할 수 있다. ExecutorService는 일정 수의 스레드를 미리 생성하여 작업을 처리한다.
1) ExecutorService 사용 예
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> {
System.out.println("Task 1 executed");
});
executor.submit(() -> {
System.out.println("Task 2 executed");
});
executor.submit(() -> {
System.out.println("Task 3 executed");
});
executor.shutdown(); // 스레드 풀 종료
}
}
ExecutorService를 사용하면 스레드를 효율적으로 관리하고, 불필요한 스레드 생성과 종료를 방지할 수 있다.
8. 멀티스레드 최적화 기법
- Thread-local 변수 사용: 멀티스레드 환경에서 공유 자원을 사용해야 할 때, 각 스레드가 독립적인 값을 가지도록 ThreadLocal을 사용한다.
- ForkJoinPool: ForkJoinPool은 큰 작업을 여러 작은 작업으로 나누어 병렬 처리하는 데 사용된다.
- Future와 Callable 사용: Runnable은 반환값이 없지만, Callable은 작업의 결과를 반환할 수 있다. Future는 작업이 끝난 후 결과를 가져오는 데 사용된다.
9. 멀티스레드 관련 문제
- 교착 상태(Deadlock): 여러 스레드가 서로의 자원을 기다리고 있어 실행이 멈추는 상태.
- 활성화 부족(Livelock): 스레드가 계속해서 상태를 변경하지만 진행되지 않는 상태.
- 스타베이션(Starvation): 스레드가 계속 실행되지 않고 대기하는 상태, 우선순위가 낮은 스레드가 자원을 얻지 못하는 상황.
'Language > Java' 카테고리의 다른 글
| [JAVA] SOLID 원칙 (0) | 2025.05.04 |
|---|---|
| [JAVA] 상속 (0) | 2025.05.04 |
| [JAVA] 클래스 (0) | 2025.05.04 |
| [JAVA] 변수 위치 및 메모리 구조 (0) | 2025.05.04 |
| [JAVA] JVM (0) | 2025.05.04 |