1. 스레드(thread)란 무엇인가
독립적인 작업 흐름으로서의 스레드
Java(그리고 일반적인 프로그래밍)에서 스레드는 하나의 프로그램 내부에서 다른 스레드들과 병행하여 실행되는 독립적인 명령 시퀀스입니다. 공장을 떠올려 보세요. 각 작업자에게는 자신의 작업대와 할 일이 있고 독립적으로 일하지만, 결과는 모두 합쳐집니다.
기본적으로 Java 프로그램은 하나의 스레드, 즉 main 메서드를 실행하는 스레드로 시작합니다. 하지만 필요한 경우 추가 스레드를 만들어 프로그램의 서로 다른 부분을 동시에 실행할 수 있습니다.
프로세스와 스레드: 무엇이 다른가?
- 프로세스 — “무거운” 실행 단위입니다. 각 프로세스는 자신의 메모리 공간, 변수, 리소스를 가집니다. 프로세스들은 서로 완전히 격리되어 있어서 하나가 “망가져도” 다른 프로세스들은 영향을 받지 않습니다.
- 스레드(thread) — 프로세스 내부의 “가벼운” 실행 단위입니다. 하나의 프로세스에 속한 모든 스레드는 메모리와 리소스를 공유합니다. 즉, 데이터를 쉽게 주고받을 수 있지만(그리고 안타깝게도 서로에게 쉽게 방해가 될 수도 있습니다).
비유:
프로세스는 각각 독립된 아파트와 같습니다. 각자 벽도 거주자도 다릅니다.
스레드는 한 아파트 안의 거주자들입니다. 각자 할 일은 다르지만 주방과 욕실은 공동으로 사용합니다.
Java에서는 어떻게 보이나요?
프로그램을 실행하면 JVM은 최소한 하나의 스레드, 즉 메인(main) 스레드를 만듭니다. 그리고 여러분은 새로운 스레드를 만들어 작업을 병렬로 수행할 수 있습니다.
2. 왜 멀티스레딩이 필요한가
반응성: UI가 멈춰서는 안 된다
예를 들어 텍스트 에디터 같은 그래픽 프로그램을 만든다고 해봅시다. 사용자가 “저장” 버튼을 눌렀고, 여러분은 긴 파일 저장 작업을 시작했습니다. 이 모든 일을 메인 스레드에서 처리하면 프로그램 창이 “얼어붙어” 사용자가 아무것도 누를 수 없고, 커서도 움직이지 않으며, 인터페이스가 반응하지 않게 됩니다. 반면 저장을 별도 스레드에서 처리하면 인터페이스는 계속 반응하고 사용자는 심지어 마음을 바꿔 프로그램을 닫을 수도 있습니다.
생활 속 예:
브라우저로 큰 파일을 다운로드한다고 해봅시다. 브라우저가 스레드를 사용하지 않는다면 파일이 다 내려받아질 때까지 새 탭을 열거나 페이지를 스크롤하는 일조차 할 수 없을 것입니다!
데이터 병렬 처리
예를 들어 처리해야 할 파일이 천 개 있다고 해봅시다(해시를 다시 계산하거나 텍스트를 치환하는 등). 왜 병렬로 처리하지 않겠습니까? 각 스레드가 자기 파일을 독립적으로 처리하면 전체 작업이 몇 배나 빨리 끝납니다.
예:
서버는 수백 명의 클라이언트 요청을 처리합니다. 서버가 이 일을 단일 스레드에서만 처리한다면 나머지 클라이언트는 끝없이 자신의 차례를 기다려야 할 겁니다. 스레드를 사용하면 각 요청이 독립적으로 처리됩니다!
멀티코어 프로세서 활용
현대의 프로세서는 하나의 “뇌”가 아니라 병렬로 일하는 여러 개의 코어입니다. 프로그램이 단일 스레드만 사용하면 나머지 코어는 무료하니 “지뢰찾기”나 하고 있을 겁니다. 반면 여러 스레드를 실행하면 모든 코어가 일을 하게 되어 프로그램이 더 빨리 실행됩니다.
흥미로운 사실:
여러분의 휴대폰도 이미 여러 코어를 가지고 있고, 노트북과 서버는 수십 개의 코어를 갖기도 합니다! 이를 모두 사용하지 않는 것은 버스를 사 놓고 혼자 타고 다니는 것과 같습니다.
3. 생활 속 예시들
| 분야 | 멀티스레딩 예 |
|---|---|
| 파일 다운로드 | 여러 파일을 동시에 다운로드 |
| 사용자 UI | 데이터 로드/저장 중에도 애플리케이션이 멈추지 않음 |
| 서버 | 다수의 네트워크 요청을 병렬로 처리 |
| 게임 | 물리, 그래픽, 음악, AI를 각자 별도 스레드로 처리 |
| 메신저 | 메시지 수신, 파일 전송, UI 업데이트 |
| 비디오 처리 | 프레임을 병렬로 처리 |
미니 비유:
요리사는 수프를 끓이고, 동시에 오븐은 파이를 굽고, 로봇 청소기는 바닥을 청소합니다. 모든 일이 동시에 일어나니 저녁 준비가 더 빨라집니다!
4. 멀티스레딩의 잠재적 난점
경쟁 상태(race condition)
여러 스레드가 동시에 같은 변수를 변경하면 결과가 예측 불가능해질 수 있습니다. 예를 들어 두 스레드가 동시에 공유 카운터를 증가시키면 최종 값이 올바르지 않을 수 있습니다. 이에 대해서는 다음 강의에서 더 자세히 다루겠습니다.
동기화
스레드들이 서로 방해하지 않도록 “누가 언제 데이터를 바꿀 수 있는지”에 대한 합의가 필요합니다. 이를 동기화라고 하며, 이를 위해 synchronized, 락 등과 같은 전용 키워드와 구문이 있습니다. 자세한 내용은 뒤에서 살펴보겠습니다.
데드락(deadlock, 상호 교착)
때로는 스레드들이 서로를 기다리느라 영원히 멈춰 버려 프로그램이 정지할 수 있습니다. 이를 데드락이라고 하며, 멀티스레드 프로그래밍에서 가장 교묘한 오류 중 하나입니다.
디버깅과 테스트
멀티스레드 프로그램의 버그를 잡는 일은 매우 어렵습니다. 어떤 때는 잘 되고, 어떤 때는 안 되기도 합니다. 어떤 버그는 서버나 사용자 환경에서만 나타나고, 여러분의 컴퓨터에서는 완벽히 작동할 수도 있습니다. 이 때문에 멀티스레드 코드의 테스트와 디버깅은 개발자에게 진정한 퀘스트가 됩니다.
5. 요약: 멀티스레드 프로그램은 어떻게 보이나요
스레드 없이 동작하는 예:
public class Main {
public static void main(String[] args) {
// 5까지 센다
for (int i = 1; i <= 5; i++) {
System.out.println(i);
}
// 문자 출력
for (char c = 'A'; c <= 'E'; c++) {
System.out.println(c);
}
}
}
출력은 항상 같다:
1
2
3
4
5
A
B
C
D
E
스레드를 사용하는 예:
public class Main {
public static void main(String[] args) {
Thread numbers = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println(i);
try {
Thread.sleep(100); // 잠깐 대기
} catch (InterruptedException e) {
// 무시함
}
}
});
Thread letters = new Thread(() -> {
for (char c = 'A'; c <= 'E'; c++) {
System.out.println(c);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// 무시함
}
}
});
numbers.start();
letters.start();
}
}
출력은 섞여서 나타난다:
1
A
2
B
3
C
4
D
5
E
혹은 스레드들이 “경쟁”하면 순서는 달라질 수 있습니다. 중요한 것은 두 루프가 병렬로 실행된다는 점입니다!
6. 유용한 포인트
시각적 다이어그램: 스레드가 함께 동작하는 방식
+-------------------+ +-------------------+
| 메인 스레드 | | 두 번째 스레드 |
+-------------------+ +-------------------+
| 1 | 2 | 3 | 4 | 5 | | A | B | C | D | E |
+-------------------+ +-------------------+
| |
| 둘 다 동작함 |
| 동시에 |
+-------------------------+
Java가 내부적으로 스레드를 사용하는 곳
- 가비지 컬렉션(Garbage Collector) — 별도 스레드가 사용되지 않는 객체를 정리합니다.
- 입출력(IO) — 파일 읽기/쓰기, 네트워크 연결.
- 서버와 웹 애플리케이션 — 각 클라이언트 요청을 별도 스레드에서 처리합니다.
- 타이머, 작업 스케줄러 — 예약된 작업을 실행합니다.
7. 초보자가 자주 하는 실수
오류 №1: 스레드가 항상 프로그램을 빠르게 만든다고 기대함.
사실 단일 프로세서 머신이거나 작업을 잘못 구성했다면, 스레드는 문맥 전환 등 오버헤드와 “혼선” 때문에 오히려 성능을 떨어뜨릴 수 있습니다.
오류 №2: 동기화 문제를 무시함.
“그냥 스레드 두 개 돌리면 되지, 뭐가 문제겠어?”라고 생각하기 쉽습니다. 하지만 두 스레드가 같은 변수를 변경하면 결과는 전혀 예상과 다를 수 있습니다.
오류 №3: 닥치는 대로 스레드를 사용함.
사소한 일마다 별도 스레드를 만들 필요는 없습니다. 스레드는 리소스이며, 과도하게 많아지면 성능 저하나 심하면 프로그램 크래시로 이어질 수 있습니다.
오류 №4: 오류 처리가 없음.
스레드는 예외를 던질 수 있습니다(예: 파일이나 네트워크 작업). 이러한 오류를 처리하지 않으면 프로그램이 비정상 종료되거나 “멈출” 수 있습니다.
GO TO FULL VERSION