Java 멀티스레드 | Thread, Runnable, Executor
이 글의 핵심
Java 멀티스레드에 대해 정리한 개발 블로그 글입니다. class MyThread extends Thread { private String name; public MyThread(String name) { this.name = name; } @Override…
들어가며
Thread·Executor·synchronized 등으로 여러 실행 흐름을 다룹니다. 공유 객체에 대한 접근 순서를 정하지 않으면 데이터가 깨질 수 있으므로, 락·원자 변수·동시성 유틸과 함께 읽는 것이 좋습니다.
C++ std::thread·mutex와 OS 스레드 + 락이라는 큰 그림이 비슷해 비교하기 좋고, Go 고루틴처럼 스레드보다 가벼운 단위를 쓰는 언어와는 설계 선택이 다릅니다.
1. Thread 생성
방법 1: Thread 상속
class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + ": " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread("Thread-1");
MyThread t2 = new MyThread("Thread-2");
t1.start();
t2.start();
}
}
방법 2: Runnable 구현 (권장)
class MyRunnable implements Runnable {
private String name;
public MyRunnable(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + ": " + i);
}
}
}
// 사용
Thread t1 = new Thread(new MyRunnable("Thread-1"));
Thread t2 = new Thread(new MyRunnable("Thread-2"));
t1.start();
t2.start();
// 람다로 더 간결하게
Thread t3 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread-3: " + i);
}
});
t3.start();
2. ExecutorService
스레드 풀
import java.util.concurrent.*;
public class ExecutorExample {
public static void main(String[] args) {
// 고정 크기 스레드 풀
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " by " +
Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executor.shutdown();
try {
executor.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Future와 Callable
import java.util.concurrent.*;
public class FutureExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
// Callable: 반환값 있음
Callable<Integer> task = () -> {
Thread.sleep(1000);
return 42;
};
Future<Integer> future = executor.submit(task);
System.out.println("작업 진행 중...");
// 결과 대기
Integer result = future.get(); // 블로킹
System.out.println("결과: " + result);
executor.shutdown();
}
}
3. 동기화 (Synchronization)
synchronized 메서드
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Count: " + counter.getCount()); // 2000
}
}
synchronized 블록
class BankAccount {
private int balance = 0;
private final Object lock = new Object();
public void deposit(int amount) {
synchronized (lock) {
balance += amount;
}
}
public void withdraw(int amount) {
synchronized (lock) {
if (balance >= amount) {
balance -= amount;
}
}
}
public int getBalance() {
synchronized (lock) {
return balance;
}
}
}
4. 실전 예제
예제: 병렬 다운로더
import java.util.concurrent.*;
import java.util.*;
public class ParallelDownloader {
public static void main(String[] args) throws Exception {
List<String> urls = Arrays.asList(
"https://example.com/file1.txt",
"https://example.com/file2.txt",
"https://example.com/file3.txt"
);
ExecutorService executor = Executors.newFixedThreadPool(3);
List<Future<String>> futures = new ArrayList<>();
for (String url : urls) {
Future<String> future = executor.submit(() -> {
System.out.println("다운로드 시작: " + url);
Thread.sleep(1000); // 다운로드 시뮬레이션
return "완료: " + url;
});
futures.add(future);
}
for (Future<String> future : futures) {
System.out.println(future.get());
}
executor.shutdown();
}
}
정리
핵심 요약
- Thread: 스레드 생성, start()로 실행
- Runnable: 작업 정의, 람다 사용 가능
- ExecutorService: 스레드 풀, 재사용
- Future/Callable: 반환값 있는 작업
- synchronized: 동기화, 데이터 무결성
다음 단계
- Java Spring Boot
관련 글
- Java 시작하기 | JDK 설치부터 Hello World까지
- Java 변수와 타입 | 기본 타입, 참조 타입, 형변환