스레드
프로그램 내에서 동시에 실행되는 가장 작은 실행 단위입니다.
하나의 프로세스 안에서 여러 스레드가 작업을 수행할 수 있게 해줍니다.
graph TD;
A["프로세스"] --> B["스레드 1"];
A --> C["스레드 2"];
A --> D["스레드 3"];
B --> E["독립적인 실행"];
C --> F["공유 자원 사용"];
D --> G["병렬 처리"];
E --> H["스택"];
F --> I["힙 메모리"];
G --> J["CPU 시간"];
style A fill:#f9f,stroke:#333,stroke-width:2px;
style B fill:#bbf,stroke:#333,stroke-width:2px;
style C fill:#bbf,stroke:#333,stroke-width:2px;
style D fill:#bbf,stroke:#333,stroke-width:2px;
Mermaid
복사
위 다이어그램은 하나의 프로세스 안에서 여러 스레드가 동작하는 방식을 보여줍니다. 각 스레드는 독립적으로 실행되면서 동시에 프로세스의 자원을 공유할 수 있습니다.
Java 스레드
프로세스와 스레드
프로세스(Process)
프로세스는 운영체제로부터 자원을 할당받아 실행 중인 프로그램의 인스턴스입니다.
스레드(Thread)
스레드는 프로세스 내에서 실행되는 가장 작은 작업 단위입니다.
구분 | 프로세스(Process) | 스레드(Thread) |
정의 | 실행 중인 프로그램의 인스턴스 | 프로세스 내에서 실행되는 흐름 단위 |
메모리 공간 | 독립된 메모리 공간 사용 | 같은 프로세스의 메모리 공유 |
자원 공유 | 다른 프로세스와 직접 공유 불가 | 같은 프로세스 내 스레드끼리 공유 가능 |
생성 비용 | 높음 (프로세스마다 독립 자원 할당) | 낮음 (자원 공유) |
예시 | 크롬 브라우저, 이클립스 IDE 등 | 탭별 렌더링, 자동 저장 기능 등 |
스레드가 사용되는 프로그램 예시
프로그램 예시 | 스레드 역할 |
웹 브라우저 | 페이지 로딩, UI 반응, 다운로드 병행 |
게임 | 그래픽 렌더링, 사용자 입력, 사운드 처리 |
채팅 프로그램 | 메시지 수신/송신을 각각 스레드로 처리 |
서버 프로그램 | 다수의 클라이언트 요청을 동시에 처리 |
스레드 구현하기
구현 방법 | 설명 |
Thread 클래스 상속 | run() 메소드 오버라이드 |
Runnable 인터페이스 구현 | run() 메소드 구현 후 Thread 객체에 전달 |
예시 코드 1: Thread 클래스 상속
class MyThread extends Thread {
public void run() {
System.out.println("Thread 실행 중!");
}
}
public class Main {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // run() 직접 호출 X
}
}
Java
복사
예시 코드 2: Runnable 인터페이스 구현
class MyRunnable implements Runnable {
public void run() {
System.out.println("Runnable 실행!");
}
}
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
Java
복사
스레드 주요 메소드
메소드 | 설명 |
start() | 새로운 스레드를 시작하고 run() 실행 |
run() | 스레드의 실행 로직 정의 |
sleep(ms) | 지정한 시간(ms) 동안 스레드를 일시정지 |
join() | 해당 스레드가 종료될 때까지 대기 |
interrupt() | 스레드에 인터럽트(작업 중단) 신호 전달 |
isAlive() | 스레드가 실행 중인지 확인 |
setPriority(int) | 스레드의 우선순위를 설정 (1~10) |
getName() / setName() | 스레드 이름 조회/설정 |
멀티스레드 (Multi-thread)
하나의 프로그램에서 여러 스레드를 동시에 실행하는 것
특징
•
CPU 활용도를 극대화
•
동시에 여러 작업 처리 가능
•
단, 동기화 문제와 경쟁 상태(Race Condition) 발생 가능
예시 코드
class Task extends Thread {
public void run() {
for(int i = 1; i <= 5; i++) {
System.out.println(getName() + " → " + i);
}
}
}
public class MultiThreadTest {
public static void main(String[] args) {
new Task().start();
new Task().start();
}
}
Java
복사
동기화(Synchronization)란?
여러 스레드가 공유 자원에 동시에 접근할 때 발생하는 문제를 방지하기 위한 기술
문제 상황 | 예시 |
동기화 X | 두 스레드가 동시에 같은 변수 접근 → 데이터 충돌 |
동기화 O | 한 스레드가 작업 중이면 다른 스레드는 대기 |
동기화 예시: Vector vs ArrayList
Vector와 ArrayList의 차이점
특징 | Vector | ArrayList |
동기화 | 동기화됨 (Thread-safe) | 동기화되지 않음 (Thread-unsafe) |
성능 | 느림 (동기화 오버헤드) | 빠름 |
멀티스레드 환경 | 안전 | 수동 동기화 필요 |
사용 권장 | 레거시 코드 | 단일 스레드 또는 수동 동기화 |
예시 1: Vector (동기화됨)
import java.util.Vector;
class VectorExample {
public static void main(String[] args) {
Vector<Integer> vector = new Vector<>();
// 스레드 1: 요소 추가
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
vector.add(i);
}
});
// 스레드 2: 요소 추가
Thread t2 = new Thread(() -> {
for (int i = 1000; i < 2000; i++) {
vector.add(i);
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Vector 크기: " + vector.size());
// 출력: Vector 크기: 2000 (동기화되어 안전함)
}
}
Java
복사
예시 2: ArrayList (동기화되지 않음)
import java.util.ArrayList;
class ArrayListExample {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
// 스레드 1: 요소 추가
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
list.add(i);
}
});
// 스레드 2: 요소 추가
Thread t2 = new Thread(() -> {
for (int i = 1000; i < 2000; i++) {
list.add(i);
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ArrayList 크기: " + list.size());
// 출력: ArrayList 크기: 1950 (예시 - 동기화되지 않아 데이터 손실 가능)
// 실행할 때마다 결과가 다를 수 있음
}
}
Java
복사
예시 3: ArrayList에 동기화 적용
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class SynchronizedArrayListExample {
public static void main(String[] args) {
// Collections.synchronizedList()로 동기화된 리스트 생성
List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
syncList.add(i);
}
});
Thread t2 = new Thread(() -> {
for (int i = 1000; i < 2000; i++) {
syncList.add(i);
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("동기화된 ArrayList 크기: " + syncList.size());
// 출력: 동기화된 ArrayList 크기: 2000 (안전함)
}
}
Java
복사
•
Vector: 모든 메소드가 synchronized로 보호되어 멀티스레드 환경에서 안전하지만 성능이 느림
•
ArrayList: 동기화되지 않아 빠르지만 멀티스레드 환경에서는 데이터 충돌 가능
•
권장 방법: ArrayList를 사용하고 필요시 Collections.synchronizedList()나 CopyOnWriteArrayList 사용
synchronized 키워드
사용 위치 | 설명 |
메소드 선언부 | 메소드 전체를 임계 구역으로 설정 |
블록 내부 | 특정 코드 블록만 임계 구역으로 설정 |
예시
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() { return count; }
}
Java
복사
임계 구역(Critical Section)이란?
임계 구역은 여러 스레드가 동시에 접근하면 안 되는 코드 영역을 의미합니다. 공유 자원(변수, 객체 등)을 수정하는 부분이 임계 구역에 해당합니다.
임계 구역의 특징
•
상호 배제(Mutual Exclusion): 한 번에 하나의 스레드만 임계 구역에 진입 가능
•
동기화 필요: synchronized 키워드로 임계 구역을 보호
•
데이터 일관성 보장: 여러 스레드가 동시에 접근해도 데이터 충돌 방지
임계 구역 예시
class BankAccount {
private int balance = 1000;
// 임계 구역: 잔액을 수정하는 메소드
public synchronized void withdraw(int amount) {
if (balance >= amount) {
balance -= amount;
System.out.println("출금: " + amount + ", 잔액: " + balance);
}
}
// 임계 구역: 잔액을 수정하는 메소드
public synchronized void deposit(int amount) {
balance += amount;
System.out.println("입금: " + amount + ", 잔액: " + balance);
}
}
Java
복사
synchronized 키워드는 메소드나 블록을 임계 구역으로 지정하여, 한 번에 하나의 스레드만 해당 영역을 실행할 수 있도록 보장합니다.
스레드 대기 (wait)
스레드를 일시 정지 상태로 만들고, 다른 스레드의 notify() 또는 notifyAll()을 기다리는 상태
메소드 | 설명 |
wait() | 스레드를 일시 정지(모니터 소유권 해제) |
notify() | 일시 정지된 스레드 중 하나를 깨움 |
notifyAll() | 일시 정지된 모든 스레드를 깨움 |
예시
class Shared {
synchronized void waitExample() throws InterruptedException {
System.out.println("대기 중...");
wait(); // 다른 스레드가 notify()할 때까지 대기
System.out.println("깨워짐!");
}
synchronized void notifyExample() {
notify(); // 하나의 스레드를 깨움
}
}
Java
복사
스레드 알림
일시 정지 상태에 있는 스레드를 깨워서 다시 실행 가능한 상태로 만드는 것을 의미합니다.
메소드 | 설명 |
notify() | 대기 중인 스레드 중 하나를 임의로 선택하여 깨움 |
notifyAll() | 대기 중인 모든 스레드를 깨움 |
notify() vs notifyAll() 차이점
•
notify(): 대기 중인 여러 스레드 중 하나만 깨우므로 효율적이지만, 어떤 스레드가 깨워질지 알 수 없음
•
notifyAll(): 모든 대기 스레드를 깨우므로 안전하지만, 불필요한 스레드까지 깨워 성능 저하 가능
예시
class NotificationExample {
private Object lock = new Object();
// 알림을 보내는 스레드
class Notifier extends Thread {
public void run() {
synchronized (lock) {
System.out.println("알림 보내는 중...");
lock.notify(); // 하나의 대기 스레드를 깨움
// lock.notifyAll(); // 모든 대기 스레드를 깨움
}
}
}
// 알림을 기다리는 스레드
class Waiter extends Thread {
public void run() {
synchronized (lock) {
try {
System.out.println("알림 대기 중...");
lock.wait(); // 알림을 받을 때까지 대기
System.out.println("알림 받음!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Java
복사
•
wait(), notify(), notifyAll()은 반드시 synchronized 블록 안에서 호출해야 함
•
그렇지 않으면 IllegalMonitorStateException 발생
•
동일한 객체의 모니터를 사용해야 스레드 간 통신이 가능
멀티스레드를 사용한 자바 프로그램 예시
생산자-소비자 문제 (Producer–Consumer)
class DataBox {
private String data;
private boolean empty = true;
public synchronized void put(String data) {
while (!empty) {
try { wait(); } catch (InterruptedException e) {}
}
this.data = data;
empty = false;
System.out.println("생산: " + data);
notifyAll();
}
public synchronized void get() {
while (empty) {
try { wait(); } catch (InterruptedException e) {}
}
System.out.println("소비: " + data);
empty = true;
notifyAll();
}
}
class Producer extends Thread {
private DataBox box;
public Producer(DataBox box) { this.box = box; }
public void run() {
for (int i = 1; i <= 5; i++)
box.put("Data-" + i);
}
}
class Consumer extends Thread {
private DataBox box;
public Consumer(DataBox box) { this.box = box; }
public void run() {
for (int i = 1; i <= 5; i++)
box.get();
}
}
public class ThreadExample {
public static void main(String[] args) {
DataBox box = new DataBox();
new Producer(box).start();
new Consumer(box).start();
}
}
Java
복사
스레드 개수 비교 예시
•
단일 스레드
•
스레드 2개
•
스레드 4개
%%{init: {'theme':'base'}}%%
xychart-beta
title "스레드 수별 실행 시간 비교"
x-axis ["단일 스레드", "스레드 2개", "스레드 4개"]
y-axis "실행 시간(ms)" 0 --> 200
bar [180, 100, 80]Mermaid
복사
public class MultiJoinTest extends Thread {
long start, end; // 스레드 시작값, 종료값
long sum; // 합계
public MultiJoinTest(long start, long end) {
this.start = start;
this.end = end;
}
@Override
public void run() {
for (long i = start; i <= end ; i++) {
sum += i;
}
}
public static void main(String[] args) throws InterruptedException {
long totalSum = 1_000_000_000L; // 10억
// 1. 단일 스레드
long startTime = System.currentTimeMillis();
long sumSingle = 0;
for (long i = 1; i <= totalSum; i++) {
sumSingle += i;
}
long endTime = System.currentTimeMillis();
System.out.println("단일 스레드 합계 : " + sumSingle);
System.out.println("실행 시간(ms) : " + (endTime - startTime));
// 2. 스레드 2개
startTime = System.currentTimeMillis();
MultiJoinTest t1 = new MultiJoinTest(1, totalSum/2);
MultiJoinTest t2 = new MultiJoinTest(totalSum/2 + 1, totalSum);
t1.start();
t2.start();
t1.join(); // 메인 스레드를 t1 스레드 종료까지 일시정지
t2.join(); // 메인 스레드를 t2 스레드 종료까지 일시정지
// 다시 메인 스레드 시작
long sum2 = t1.sum + t2.sum;
endTime = System.currentTimeMillis();
System.out.println("스레드 2개 합계 : " + sum2);
System.out.println("실행 시간(ms) : " + (endTime - startTime));
// 3. 스레드 4개
startTime = System.currentTimeMillis();
long part = totalSum / 4;
MultiJoinTest s1 = new MultiJoinTest(1, part);
MultiJoinTest s2 = new MultiJoinTest(part + 1, part*2);
MultiJoinTest s3 = new MultiJoinTest(part*2 + 1, part*3);
MultiJoinTest s4 = new MultiJoinTest(part*3 + 1, totalSum);
s1.start();
s2.start();
s3.start();
s4.start();
s1.join();
s2.join();
s3.join();
s4.join();
long sum3 = s1.sum + s2.sum + s3.sum + s4.sum;
endTime = System.currentTimeMillis();
System.out.println("스레드 4개 합계 : " + sum3);
System.out.println("실행 시간(ms) : " + (endTime - startTime));
}
}
Java
복사
핵심 정리
키워드 | 의미 |
Thread, Runnable | 스레드 구현 방법 |
start() | 스레드 실행 시작 |
synchronized | 동기화 처리 |
wait() / notify() | 스레드 간 통신 |
join() | 특정 스레드 종료까지 대기 |
JavaFX 에서 스레드 사용하기
JavaFX UI 스레드
JavaFX 애플리케이션에서 UI 관련 작업은 주 스레드(JavaFX Application Thread)에서 실행되어야 합니다.
Platform.runLater(() -> {
// UI 업데이트 코드
label.setText("데이터 수신 완료");
});
Java
복사
소켓 통신 스레드
소켓 통신은 별도의 스레드에서 처리하여 UI가 블로킹되지 않도록 합니다.
new Thread(() -> {
try (Socket socket = new Socket("localhost", 8080)) {
// 소켓 통신 코드
InputStream input = socket.getInputStream();
// 데이터 수신 후 UI 업데이트
Platform.runLater(() -> updateUI(data));
} catch (IOException e) {
e.printStackTrace();
}
}).start();
Java
복사
스레드 간 통신
•
소켓 스레드에서 데이터를 수신하면 Platform.runLater()를 사용하여 UI 스레드에 업데이트를 요청
•
데이터는 스레드 안전한 컬렉션(예: ConcurrentHashMap)을 사용하여 공유
•
스레드 간 동기화를 위해 synchronized 블록이나 Lock 사용
이러한 구조를 통해 네트워크 작업이 UI의 반응성에 영향을 주지 않으면서 효율적인 데이터 처리가 가능합니다.




