본문 바로가기
won2dev-log
HomeArchiveTagsCategoriesAboutProjects
HomeArchiveTagsCategoriesAboutProjects
won2dev-logwon2dev-logwon2dev-log

비전공 개발자의 로그 | won2dev-log

Navigation
  • Home
  • Archive
  • About
  • Projects
Categories
  • Docs
  • TIL
  • Automation
  • Git · GitHub
  • Project
Tags
  • TIL
  • Java
  • Spring
  • Backend
  • n8n
더보기
About

기록을 거름 삼아 공유는 성장을 만든다.

LicensePrivacy
© won2dev 2026. All rights reserved.
Home›Docs›TIL - Java 기초: 쓰레딩 시작하기
Docs

TIL - Java 기초: 쓰레딩 시작하기

won2dev·2025년 03월 22일
#Java#TIL
TIL - Java 기초: 쓰레딩 시작하기

💬 배운 기술 / 지식

  • 싱글 쓰레드(Single Thread)와 멀티 쓰레드(Multithreading) 개념
  • 쓰레드(Thread)란? 프로세스와의 차이
  • 쓰레드 생성 방법 (Thread 상속, Runnable 구현)
  • 쓰레드 실행과 관리
  • 쓰레드 상태와 라이프사이클
  • 동기화(Synchronization)와 쓰레드 안전(Thread Safety)
  • 멀티 쓰레딩의 장단점과 주의점

🧵 싱글 쓰레드 vs 멀티 쓰레드

구분싱글 쓰레드 (Single Thread)멀티 쓰레드 (Multithreading)
개념프로그램 내 작업을 한 번에 하나씩 순차 실행여러 작업을 동시에(또는 빠르게 번갈아가며) 실행
처리 방식한 줄씩 순서대로 처리여러 쓰레드가 병렬 또는 동시 처리 시도
장점구조가 단순하고 디버깅 쉬움CPU 활용 극대화, 프로그램 응답성 및 속도 향상
단점느린 처리, 응답 지연 발생 가능설계 복잡, 동기화 문제로 인한 버그 발생 위험
사용 예단순 계산, 일괄 처리GUI, 웹서버, 게임, 실시간 데이터 처리 등

🔎 쓰레드(Thread)란?

  • 프로세스(Process) 내에서 실제 작업을 수행하는 실행 흐름의 단위
  • 프로세스는 운영체제로부터 할당받은 자원(메모리 등)을 가진 실행 환경
  • 한 프로세스 내에서 여러 쓰레드를 생성해 작업 병렬 처리 가능
  • 자바에서는 java.lang.Thread 클래스로 쓰레드를 관리

🛠 쓰레드 생성 방법

1. Thread 클래스 상속

java
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(getName() + " 실행 중: " + i);
            try {
                Thread.sleep(300); // 0.3초 쉬기
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(getName() + " 실행 중: " + i);
            try {
                Thread.sleep(300); // 0.3초 쉬기
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2. Runnable 인터페이스 구현

java
class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + " 실행 중: " + i);
            try {
                Thread.sleep(300); // 0.3초 쉬기
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + " 실행 중: " + i);
            try {
                Thread.sleep(300); // 0.3초 쉬기
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

🏃‍♂️ 쓰레드 실행

java
public class ThreadDemo {
    public static void main(String[] args) {
        // Thread 상속 사용
        MyThread thread1 = new MyThread();
        thread1.setName("Thread-1");
        thread1.start();  // run() 대신 start() 호출해야 쓰레드 실행

        // Runnable 구현 사용
        Thread thread2 = new Thread(new MyRunnable());
        thread2.setName("Thread-2");
        thread2.start();
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        // Thread 상속 사용
        MyThread thread1 = new MyThread();
        thread1.setName("Thread-1");
        thread1.start();  // run() 대신 start() 호출해야 쓰레드 실행

        // Runnable 구현 사용
        Thread thread2 = new Thread(new MyRunnable());
        thread2.setName("Thread-2");
        thread2.start();
    }
}

🔄 쓰레드 상태 및 라이프사이클

상태설명
New쓰레드 객체 생성 후 start() 호출 전 상태
Runnable실행 대기 또는 실행 중 상태
Blocked락(lock) 획득 대기 상태
Waiting다른 쓰레드 작업 완료 대기 상태
Timed Waiting일정 시간 동안 대기 상태
Terminatedrun() 메서드 종료로 쓰레드 작업 완료 상태

🔐 동기화(Synchronization)와 쓰레드 안전성

  • 여러 쓰레드가 동시에 같은 자원(변수, 객체 등)을 수정할 때 데이터 불일치, 경쟁 상태(Race Condition)발생 가능
  • synchronized 키워드로 동기화 영역 지정 → 한 번에 한 쓰레드만 접근 허용
  • 공유 자원 접근을 보호해 쓰레드 안전(Thread Safety) 보장
java
class SafeCounter {
    private int count = 0;

    // 동기화 메서드로 안전하게 증가 처리
    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}
class SafeCounter {
    private int count = 0;

    // 동기화 메서드로 안전하게 증가 처리
    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

⚠️ 동기화가 필요한 이유와 문제점

  • 동기화 없으면 변수 증감 시 잘못된 결과 발생 가능
  • 하지만 동기화는 성능 저하를 유발할 수 있으므로 필요한 부분에만 적용 권장
  • Deadlock(교착 상태) 같은 문제 주의 필요

📝 코드 예제

java
public class ThreadingExample {

    // 동기화 처리된 카운터 클래스
    static class Counter {
        private int count = 0;

        public synchronized void increment() {
            count++;
        }

        public int getCount() {
            return count;
        }
    }

    // Runnable 구현, Counter 공유
    static class Worker implements Runnable {
        private Counter counter;

        public Worker(Counter counter) {
            this.counter = counter;
        }

        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                counter.increment();
                System.out.println(Thread.currentThread().getName() + " 증가시킨 카운트: " + counter.getCount());
                try {
                    Thread.sleep(200); // 잠시 쉬기
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        Counter sharedCounter = new Counter();

        Thread t1 = new Thread(new Worker(sharedCounter));
        Thread t2 = new Thread(new Worker(sharedCounter));

        t1.setName("쓰레드-A");
        t2.setName("쓰레드-B");

        t1.start();
        t2.start();
    }
}
public class ThreadingExample {

    // 동기화 처리된 카운터 클래스
    static class Counter {
        private int count = 0;

        public synchronized void increment() {
            count++;
        }

        public int getCount() {
            return count;
        }
    }

    // Runnable 구현, Counter 공유
    static class Worker implements Runnable {
        private Counter counter;

        public Worker(Counter counter) {
            this.counter = counter;
        }

        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                counter.increment();
                System.out.println(Thread.currentThread().getName() + " 증가시킨 카운트: " + counter.getCount());
                try {
                    Thread.sleep(200); // 잠시 쉬기
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        Counter sharedCounter = new Counter();

        Thread t1 = new Thread(new Worker(sharedCounter));
        Thread t2 = new Thread(new Worker(sharedCounter));

        t1.setName("쓰레드-A");
        t2.setName("쓰레드-B");

        t1.start();
        t2.start();
    }
}

💡 느낀 점 / 참고 사항

  • 쓰레딩은 프로그램 효율성 향상을 위한 핵심 기술이나, 복잡성과 버그 위험도 증가
  • 싱글 쓰레드의 단순함과 멀티 쓰레드의 병렬 처리 장점을 상황에 맞게 선택해야 함
  • 공유 자원 동기화가 필수지만, 과도한 동기화는 성능 저하를 초래하므로 신중한 설계 필요
  • 최신 자바에서는 ExecutorService 같은 쓰레드 풀 관리 API 사용 권장
공유하기
이전 글Java Calculator Project다음 글 TIL - Java 기초: 스트림(Stream)으로 데이터 다루기

목차

  • 💬 배운 기술 / 지식
  • 🧵 싱글 쓰레드 vs 멀티 쓰레드
  • 🔎 쓰레드(Thread)란?
  • 🛠 쓰레드 생성 방법
  • 1. Thread 클래스 상속
  • 2. Runnable 인터페이스 구현
  • 🏃‍♂️ 쓰레드 실행
  • 🔄 쓰레드 상태 및 라이프사이클
  • 🔐 동기화(Synchronization)와 쓰레드 안전성
  • ⚠️ 동기화가 필요한 이유와 문제점
  • 📝 코드 예제
  • 💡 느낀 점 / 참고 사항

카테고리

Docs

태그

#Java#TIL

최근 글

Git 요약 (1) - Rebase, Stash, Squash MergeTIL - MSA 핵심 요소 정리TIL - (4) Spring 어노테이션 정리: Mockito를 활용한 단위 테스트TIL - (3) Spring 어노테이션 정리: Lombok Getter, Setter와 생성자TIL - (2) Spring MVC와 WebFlux의 차이: 블로킹과 논블로킹