Java Thread

김소희·2025년 11월 10일

스레드란?

  • 한 프로그램(프로세스) 안에서 실행 흐름을 나누는 단위 (최소 실행 단위)
  • main()이 실행할 때도 사실 main Thread가 존재하는 것
  • 자바의 멀티스레드는 공유 Heap 위에서 각 스레드마다 개별 Stack을 가지고
    여러 실행 흐름을 동시에 관리하는 기술

프로그램을 실행하면 프로세스가 되는데 프로세스는 최소 하나의 스레드가 있어야 하며 따라서 최소 하나의 스택메모리가 있어야한다.

구분특징
싱글 스레드main()만 실행 → 순차적으로 흐름 1개
멀티 스레드여러 흐름이 동시에 존재 → 누가 먼저 끝날지 예측 불가

멀티 스레드의 예시로 카카오톡에서 새로운 메세지를 받으면서 타이핑을 칠 수 있는 것을 떠올리면 좋다.

동시성과 병렬성

개념설명
동시성(Concurrency)CPU 1개가 여러 작업을 번갈아가며 처리 → 동시에 하는 것처럼 ‘보여짐’
병렬성(Parallelism)CPU 여러 개가 여러 작업을 실제로 동시에 처리

스레드를 만드는 방법

Java 에서 스레드를 만드는 대표적인 3가지 방식은 아래와 같다.

방식설명
Task_1Thread 클래스를 직접 상속
Task_2Runnable 인터페이스 구현 → Thread 생성자에 전달
익명 구현Runnable 익명객체를 바로 new Thread() 에 넣는 방식

왜 방식이 나뉘었을까?
자바는 “다중 상속 불가” 이기 때문이다.

이미 다른 클래스를 상속 중인 상태에서는 Thread 를 직접 상속할 수 없으므로
Runnable 을 구현하도록 설계되어 있다.

실제 스레드 실행은 main() 에서 start() 로 시작한다.


코드 예제

/*
Thread : 프로세스에서 하나의 최소 실행단위(흐름)
run() : Thread 의 main 역할, 새로 생성된 stack 의 첫 프레임에 올라감
*/

class Task_1 extends Thread {   // 1) Thread 직접 상속

    @Override
    public void run() {
        for(int i = 0 ; i < 100 ; i++){
            System.out.println("Task_1 : " + i + "   >> " + this.getName());
        }
        System.out.println("Task_1 run() 함수 END");
    }
}


class Task_2 implements Runnable {  // Thread 가 아니다. run()만 가지고 있는 클래스.

    @Override
    public void run() {
        for(int i = 0 ; i < 100 ; i++){
            System.out.println("Task_2 : " + i);
        }
        System.out.println("Task_2 run() 함수 END");
    }
}



public class Ex02_Multi_Thread {

    public static void main(String[] args) {

        // 1) Thread 상속 사용
        Task_1 t1 = new Task_1();

        // 2) Runnable 구현 사용
        Task_2 task2 = new Task_2();
        Thread t2 = new Thread(task2);  // Runnable 은 Thread 에 넣어서 실행해야 실제 스레드 됨
        
        // 3) 익명객체 사용
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0 ; i < 100 ; i++){
                    System.out.println("Task_3 : " + i + "   >> " + Thread.currentThread().getName());
                }
                System.out.println("Task_3 run() 함수 END");
            }
        });

        // 실행 (run() 직접 호출 X, 반드시 start())
        t1.start();
        t2.start();
        t3.start();

        // main thread
        for(int i = 0 ; i < 100 ; i++){
            System.out.println("main : " + i);
        }
        System.out.println("main END");
    }
}

run() / start() 차이

메서드역할
run()스레드가 실제로 수행할 코드
start()JVM 에게 “새 스레드 하나 만들어서 run() 올려주세요” 라고 요청

run() 은 원래 Runnable 인터페이스에서 추상 메서드였다.
하지만 Thread 클래스가 기본 구현을 이미 제공하고 있기 때문에 Thread 의 run() 은 abstract 가 아니다.

즉 run() 은 “반드시 오버라이드하도록 강제하는 함수가 아니다.”

그리고 run() 을 직접 호출하면 단순 메서드 호출일 뿐이며
새로운 스레드는 생성되지 않는다 → 그래서 반드시 start() 를 써야 한다.


스레드 실행 결과

세 개의 스레드(t1 / t2 / t3) 중 누가 먼저 끝날까?

정답: 알 수 없다.

실행할 때마다 결과는 달라진다.
왜냐하면 CPU 를 점유했다가 뺏기는 과정을 OS 스케줄러가 결정하기 때문이다.

즉 아래 스샷처럼 출력 순서는 계속 랜덤하게 섞인다:

멀티스레드는 동작과 종료 순서를 개발자가 결정할 수 없다.
OS 스케줄링에 따라 CPU 점유가 계속 바뀌기 때문이다.


멀티 스레드 게임 만들기

package org.basic.util;

import javax.swing.JOptionPane;

public class MultiThread_GuGuDan {

    static boolean timeOut = false;

    public static void main(String[] args) {

        int totalCount = 0;   // 총 문제 수
        int correctCount = 0; // 정답 수

        Thread timer = new Thread(() -> {
            for(int i = 10; i > 0; i--){
                System.out.println("남은시간 : " + i);
                try { Thread.sleep(1000); } catch (Exception e){}
            }
            timeOut = true;
            System.out.println("시간 종료!");
        });

        timer.start();

        while(!timeOut) {  // 시간 끝날때까지 계속 문제!
            int dan = (int)(Math.random()*8)+2;
            int num = (int)(Math.random()*9)+1;
            int result = dan * num;

            String input = JOptionPane.showInputDialog(dan + " x " + num + " = ?");

            if(timeOut) break; // 입력도중 시간 종료 체크

            if(input == null || input.equals("")) continue;

            totalCount++;

            if(Integer.parseInt(input) == result){
                System.out.println("정답!");
                correctCount++;
            } else {
                System.out.println("오답!");
            }
        }

        // 여기서 최종 결과 출력
        System.out.println("========================");
        System.out.println("게임 종료");
        System.out.println("총 " + totalCount + "문제 중 " + correctCount + "문제 정답!");
        System.out.println("========================");
    }
}


동기화 (Synchronization)

멀티스레드에서 가장 중요한 키워드는 공유 자원이다.
벡터는 동기화를 보장하지만,
arrayList는 동기화를 보장하지 않으므로,
값의 변화를 인지할 수 없지만 성능에 유리하다.

예를 들어보자.

한강에서 비빔밥 축제가 열렸다고 해보자.
가운데 커다란 솥에 비빔밥이 산처럼 쌓여 있고
사람들이 둥글게 둘러앉아 한 숟가락씩 퍼먹는다.

이 때 공유 자원은 비빔밥이다.

다 같이 막 퍼먹기 시작하면
누가 많이 퍼갔는지 / 누가 적게 퍼갔는지 / 순서가 뒤섞이든
상관 없이 그냥 빠르게 먹을 수 있다.

→ 즉 “락이 없는 상태”


이번엔 다같이 비빔밥을 너~무 많이 먹어서 화장실을 찾았다.

친구들과 한강에 놀러 갔는데 화장실 칸이 딱 한 칸만 있다고 하자.
이 때 공유 자원은 변기라는 아주 직관적인 대상이다.

사람은 한 명씩 들어가서 문을 잠근다.
문을 잠근다는 건 락(lock) 을 걸었다는 의미다.

이 case 는 누가 먼저 들어가 몇 초를 쓰는지가 아주 명확해진다.
대신 변비 환자가 들어가 있으면 뒤에 줄이 길어질 수 있다.

이게 바로 “동기화(synchronization)”의 trade-off 이다.

  • 데이터는 안전하다
  • 하지만 속도는 떨어진다

동기화는 공유자원에 대한 접근 질서를 보장하기 위한 기능이다.
자바에서는 이런 동기화를 synchronized 키워드 하나로 간단히 적용할 수 있다.
synchronized : 공유자원에 한 번에 한 스레드만 접근하도록 락을 거는 키워드


예제 코드

package org.basic.util;

class Wroom {
    synchronized void openDoor(String name) {
        System.out.println(name + "님 화장실 입장 ^^");
        for (int i = 0; i < 100; i++) {
            System.out.println(name + "사용 중" + i);
            if (i == 10) {
                System.out.println(name + "님이 💩");
            }
        }
        System.out.println("시원");
    }
}

class User extends Thread {
    Wroom wr;
    String who;
    
    User(String name, Wroom wr) {
        this.who = name;
        this.wr = wr;
    }
    
    @Override
    public void run() {
        wr.openDoor(this.who);
    }
}

public class SyncThread {
    public static void main(String[] args) {
        // 여기는 한강 화장실
        Wroom w = new Wroom();
        
        User kim = new User("김씨", w);
        User Lee = new User("이씨", w);
        User Park = new User("박씨", w);

        kim.start();
        Lee.start();
        Park.start();
    }
}

참고자료

인파 블로그
도서 - 이것이 자바다
동시성과 병렬성

profile
개발자 소희의 노트

0개의 댓글