Search

스레드

스레드

프로그램 내에서 동시에 실행되는 가장 작은 실행 단위입니다.
하나의 프로세스 안에서 여러 스레드가 작업을 수행할 수 있게 해줍니다.
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 스레드

프로세스와 스레드
스레드가 사용되는 프로그램 예시
스레드 구현하기
스레드 주요 메소드
멀티스레드 (Multi-thread)
동기화(Synchronization)란?
synchronized 키워드
스레드 대기 (wait)
스레드 알림
멀티스레드를 사용한 자바 프로그램 예시

프로세스와 스레드

프로세스(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의 역할
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
복사

스레드 알림

스레드 알림(Notification)이란?
일시 정지 상태에 있는 스레드를 깨워서 다시 실행 가능한 상태로 만드는 것을 의미합니다.
메소드
설명
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의 반응성에 영향을 주지 않으면서 효율적인 데이터 처리가 가능합니다.