본문 바로가기

[Java] 멀티스레드 좋아 멀티스레드 하면 친구 생겨 친구 해줘

@Salieri2026. 2. 6. 08:45

  컴퓨터의 CPU 코어는 여러 개인데, 프로그램이 스레드를 하나만 쓴다면(싱글 스레드) 나머지 코어들은 놀게되는데, 이 코어들에게 명령을 내려 전체의 역할을 세부적으로 할당해 하나의 방향성으로 나아가게 하는 것이 Java Thread 라고 할 수 있다. 관련 포스팅은 바로 다음에 쓸 것인데, 일단 Java Thread 가 사용되는 시기를 말하면

 

 

  • 웹 서버 (Spring/Tomcat): 수천 명의 사용자가 동시에 접속할 때, 사용자 한 명당 스레드 하나를 배정하여 기다리지 않게 처리합니다.
  • UI 프로그램 (안드로이드/데스크톱 앱): 파일을 다운로드하거나 무거운 계산을 할 때 '작업 스레드'를 따로 돌려야 화면이 멈추지(프리징) 않습니다.
  • 게임: 그래픽 렌더링 스레드, 물리 엔진 스레드, 네트워크 통신 스레드를 분리하여 끊김 없는 게임 플레이를 제공한다

 

사실 이렇게 말해도 머릿속에 확! 하고 그려지지 않는데, 우리 또 백병원에 가서 공부해보자


 

이제 우리가 아는 것으로 Thread 를 빠르게 이해해보자

 

 

 

제1막: 윽, 여기가 어디요? (스레드 생성과 실행)

 

 

[장면: 백병원 301호실] 심영이 가쁜 숨을 몰아쉬며 메인 스레드에서 깨어난다

 

 

심영: (눈을 뜨며 괴로워한다) 으윽... 아... 아아... 여기가... 어디요?

의사 양반: 아, 병원이오. 안심하세요. 지독한 멀티스레드 충돌을 겪었지만, 일단 start()는 성공했소.

심영: ...병원이요?' 

의사 양반: 지독한 교착 상태(Deadlock)였소. 하마터면 프로그램이 뻗을 뻔했단 말이오.

 

💻 코드 고증: 스레드를 만드는 두 가지 운명

// 방법 1: 심영 클래스 (Thread 상속)
class SimYoung extends Thread {
    public void run() {
        System.out.println("심영: 여기가... 어디요...?");
    }
}

// 방법 2: 의사 양반 클래스 (Runnable 구현)
class DoctorTask implements Runnable {
    @Override
    public void run() {
        System.out.println("의사 양반: 안심하세요, 병원이오.");
    }
}

public class Main {
    public static void main(String[] args) {
        SimYoung sim = new SimYoung();
        Thread doc = new Thread(new DoctorTask());

        sim.start(); // 심영 실행!
        doc.start(); // 의사 양반 실행!
    }
}

제2막: 청천벽력 (스레드 상태와 절규)

심영: ...의사 양반, 그럼 나는... 이제 실행될 수 있는 거요?

의사 양반: (단호하게) 아니, 선생은 앞으로... 자원을 가질 수가 없습니다. 다시 말해서, 싱글 스레드로 전락했다 그 말입니다.

심영: (경악하며) 뭐라고?! 그게 무슨 소리요?! 내가... 내가 싱글 스레드라니! 내가 고자... 아니, 내가 싱글 스레드라니!!

간호사: 환자분, 안정을 취하세요. 지금은 TIMED_WAITING 상태입니다!

 

코드 고증: 스레드의 상태 변화 (Thread.State)

  • NEW: 생성만 된 상태 (심영이 병원에 오기 전)
  • RUNNABLE: 실행 중 (심영이 소리 지르는 중)
  • TIMED_WAITING: sleep() 중 (의사 양반이 안정을 취하라고 한 상태)
  • TERMINATED: 실행 종료 (심영의 인생... 아니 프로그램 종료)
try {
    System.out.println("심영: 아핡핡핡! 내가 싱글 스레드라니!!");
    Thread.sleep(3000); // 3초간 TIMED_WAITING (안정)
} catch (InterruptedException e) {
    System.out.println("심영: 누가 내 잠(sleep)을 깨우는 거요!");
}

제3막: 전화기 쟁탈전 (동기화와 임계 영역)

심영: 전화... 전화 좀 갖다 주시오! 어머니께 알려야 하오!

의사 양반: 안 됩니다. 지금 전화기는 공유 자원이되어 있소.

 

 코드 고증: synchronized (임계 영역)

여러 스레드가 동시에 전화기를 쓰려고 하면 데이터가 꼬입니다.

한 명씩만 쓰게 잠궈야 합니다.

class Telephone {
    // synchronized: 상두가 전화를 다 쓸 때까지 심영은 대기(Blocked)해야 함
    public synchronized void use(String name) {
        System.out.println(name + "이(가) 수화기를 들었습니다.");
        try { Thread.sleep(2000); } catch (Exception e) {}
        System.out.println(name + "이(가) 전화를 끊었습니다.");
    }
}

// 상두 스레드와 심영 스레드가 이 객체를 공유하면, 한 명씩만 통화 가능!

 제4막: 백병원의 질서 (스레드 풀)

 코드 고증: ExecutorService

매번 스레드를 만들면 메모리가 부족해집니다. 미리 만들어놓고 재사용합시다.

import java.util.concurrent.*;

// 의사 양반 3명으로 구성된 팀
ExecutorService doctorPool = Executors.newFixedThreadPool(3);

// 10명의 환자(Task)가 몰려와도 3명의 의사가 차례대로 처리
for (int i = 1; i <= 10; i++) {
    int patient = i;
    doctorPool.execute(() -> {
        System.out.println(Thread.currentThread().getName() + " 의사가 " + patient + "번 환자를 진료 중...");
    });
}

 에필로그: "공산당 할 거야, 안 할 거야!"

김두한: (갑자기 나타나 권총을 들이대며) 심영이! 너, 멀티스레드 제대로 할 거야, 안 할 거야!

심영: (겁에 질려) 안 하겠소! 동기화 오류 안 내겠소! 다신 데드락 안 걸겠소!

  1. volatile: 변수를 메인 메모리에 저장해 스레드 간 가시성을 확보하십시오. 안 그러면 김두한이 화를 낼 겁니다.
  2. wait() / notify(): 스레드끼리 서로 신호를 주고받으며 협력하십시오. 의사 양반과 심영처럼 말이죠.
  3. Deadlock: 서로 자원을 붙잡고 안 놔주면 백병원은 영원히 멈춥니다.

의사 양반: 선생은 운이 좋았소. 다음엔 Exception이 아니라 Error가 날 것이오.

심영: "아이고... 난 이제 죽었어... (프로그램 종료)"

 

 


다음 포스트에 좀 더 Thread 공부해보자 

Salieri
@Salieri :: 살리에리의 인생살이 채널

평소에 내가 좋아하는것과 싫어하는 것들 그리고 왜 안되나 싶은 것들을 업로드 하는 블로그입니다 인생살이도 업로드 하고있어요. 제가 걸어온 길이 당신에게 있어서 도움이 되는 이정표였으면 좋겠네요. 질문은 항상 넓은 마음으로 받고있답니다. 대답하기 곤란한 질문이 아닌 이상 제에게 이메일을 써주세요 트위터 : https://twitter.com/@Salieri1845799 깃허브 : https://github.com/salieri009

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차