Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- 부트캠프
- OPENPATH
- 디자인교육
- 티스토리챌린지
- 국비지원교육
- baekjoon
- mysql
- 백엔드
- 국비지원
- 패스트캠퍼스
- 내일배움카드
- Spring
- KDT
- 오블완
- 디자인챌린지
- Be
- UXUI기초정복
- 환급챌린지
- Java
- 백준
- UXUI챌린지
- 백엔드개발자
- 오픈챌린지
- 객체지향
- 국비지원취업
- 오픈패스
- 백엔드 부트캠프
- 내일배움캠프
- 디자인강의
- UXUIPrimary
Archives
- Today
- Total
군만두의 IT 공부 일지
[스터디4] 07. 멀티 스레드 본문
목차
14장. 멀티 스레드
14.6 스레드 동기화
- 멀티 스레드는 하나의 객체를 공유해서 작업할 수 있다.
- 하지만 다른 스레드에 의해 객체 내부 데이터가 쉽게 변경될 수 있기 때문에 의도했던 것과는 다른 결과가 나올 수 있다.

위 사진에서 UserThread에 저장된 데이터가 날아간다.

스레드가 사용 중인 객체를 다른 스레드가 변경할 수 없도록 스레드 작업이 끝날 때까지 객체에 잠금을 건다.
- 객체 내부에 동기화(synchronized) 메소드와 블록이 여러 개 있다면 스레드가 이 중 하나를 실행할 때, 다른 스레드는 일반 메소드는 실행 가능하지만 해당 메소드와 다른 동기화 메소드 및 블록을 실행할 수 없다.
동기화 메소드 및 블록 선언
동기화 메소드를 선언하려면 synchronized 키워드를 인스턴스나 정적 메소드에 붙인다. 스레드가 동기화 메소드를 실행하는 즉시 객체는 잠금이 일어나고, 메소드 실행이 끝나면 잠금이 풀린다.
public synchronized void method() {
// 단 하나의 스레드만 실행하는 영역
}
메소드 전체가 아닌 일부 영역을 실행할 때만 객체 잠금을 걸고 싶다면 동기화 블록을 만든다.
public void method() {
// 여러 스레드가 실행할 수 있는 영역
synchronized(공유객체) {
// 단 하나의 스레드만 실행하는 영역
}
// 여러 스레드가 실행할 수 있는 영역
}
wait()과 notify()를 이용한 스레드 제어
- 두 개의 스레드를 교대로 번갈아 가며 실행할 때도 있다.
- 정확한 교대 작업이 필요한 경우, 자신의 작업이 끝나면 상대방 스레드를 일시 정지 상태에서 풀어주고 자신은 일시 정지 상태로 만든다.
- 공유 객체는 두 스레드가 작업할 내용을 각각 동기화 메소드로 정해 놓는다.
- notify() 메소드: wait()에 의해 일시 정지된 스레드 중 한 개를 실행 대기 상태로 만든다.
- notifyAll() 메소드: wait()에 의해 일시 정지된 모든 스레드를 실행 대기 상태로 만든다.
- wait() 메소드: 일시 정지 상태로 만든다.

14.7 스레드 안전 종료
- 스레드는 자신의 run() 메소드가 모두 실행되면 자동적으로 종료되지만, 경우에 따라 실행 중인 스레드를 즉시 종료할 필요가 있다.
- 예) 동영상을 끝까지 보지 않고 사용자가 멈춤을 요구하는 경우
- Thread는 stop() 메소드를 제공했으나, 스레드를 갑자기 종료하게 되면 사용 중이던 리소스(파일, 네트워크 연결 등)들이 불안정한 상태로 남겨지기 때문에 deprecated(더 이상 사용하지 않음)되었다.
- 스레드를 안전하게 종료하는 방법: 사용하던 리소스들을 정리하고 run() 메소드를 빨리 종료하는 것
- 조건 이용 방법
- interrupt() 메소드 이용 방법
조건 이용
스레드가 while 문으로 반복 실행할 경우, 조건을 이용해서 run() 메소드의 종료를 유도할 수 있다.
public class XXXThread extends Thread {
private boolean stop; // stop이 필드 선언
public void run() {
while (!stop) { // stop가 true가 되면 while 문을 빠져나감
// 스레드가 반복 실행하는 코드;
}
// 스레드가 사용한 리소스 정리 // 리소스 정리
} // 스레드 종료
}
interrupt 메소드 이용
- interrupt() 메소드: 스레드가 일시 정지 상태에 있을 때 InterruptedException 예외를 발생시킨다.
- 스레드가 실행 대기/실행 상태일 때는 interrupt() 메소드가 호출되어도 InterruptedException이 발생하지 않는다.

InterruptedException이 발생하여 예외 처리 블록으로 이동하면, 결국 while 문을 빠져나와 자원을 정리하고 스레드가 종료되는 효과를 가져온다.
- interrupt() 메소드 호출 여부를 알 수 있는 방법
- Thread.sleep(1)과 같이 스레드가 어떤 이유로 일시 정지 상태가 되면, InterruptedException 예외가 발생한다.
- Thread의 정적 메소드인 interrupted()와 인스턴스 메소드인 isInterrupted() 메소드는 interrupt() 메소드 호출 여부를 리턴한다.
boolean status = Thread.interrupted();
boolean status = objThread.isInterrupted();
14.8 데몬 스레드
- 데몬(daemon) 스레드: 주 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드
- 주 스레드가 종료되면 데몬 스레드도 자동으로 종료된다.
- 예) 워드프로세서의 자동 저장, 미디어플레이어의 동영상 및 음악 재생, 가비지 컬렉터 등에서 주 스레드(워드프로세서, 미디어플레이어, JVM)가 종료되면 데몬 스레드도 종료된다.
- 스레드를 데몬으로 만들기 위해서 주 스레드가 데몬이 될 스레드의 setDaemon(true)를 호출하면 된다.
public static void main(String[] args) {
AutoSaveThread thread = new AutoSaveThread();
thread.setDaemon(true);
thread.start();
...
}
코드에서 메인 스레드는 주 스레드, AutoSaveThread는 데몬 스레드가 된다.
14.9 스레드풀
병렬 작업 처리가 많아지면 스레드의 개수가 폭증하여 CPU가 바빠지고 메모리 사용량이 늘어나면서 애플리케이션의 성능이 저하되는데, 이것을 막기 위해 스레드풀을 사용한다.
- 스레드풀(ThreadPool): 작업 처리에 사용되는 스레드를 제한된 개수만큼 정해 놓고 작업 큐(Queue)에 들어오는 작업들을 스레드가 하나씩 맡아 처리하는 방식
- 작업 처리가 끝난 스레드는 다시 작업 큐에서 새로운 작업을 가져와 처리한다.

스레드풀 생성
- 자바는 스레드풀을 생성하고 사용할 수 있도록 java.util.concurrent 패키지에서 ExecutorService 인터페이스와 Executors 클래스를 제공한다.
- Executors의 두 정적 메소드를 이용하면 스레드풀인 ExecutorService 구현 객체를 만들 수 있다.
- 초기 수: 스레드풀이 생성될 때 기본적으로 생성되는 스레드 수
- 코어 수: 스레드가 증가된 후 사용되지 않는 스레드를 제거할 때 최소한 풀에서 유지하는 스레드 수
- 최대 수: 증가되는 스레드의 한도 수
메소드명(매개변수) | 초기 수 | 코어 수 | 최대 수 |
newCachedThreadPool() | 0 | 0 | Integer.MAX_VALUE |
newFixedThreadPool(int nThreads) | 0 | 생성된 수 | nThreads |
ExecutorService executorService = Executors.newCachedThreadPool();
- 예1)
- 초기 수 0개 / 코어 수 0개
- 작업 개수가 많아지면 새 스레드를 생성시켜 작업을 처리한다.
- 60초 동안 스레드가 아무 작업을 하지 않으면 스레드를 풀에서 제거한다.
ExecutorService executorService = Executors.newFixedThreadPool(5);
- 예2)
- 초기 수 0개 / 최대 수 5개
- 생성된 스레드를 제거하지 않는다는 특징이 있다.
ExecutorService threadPool = new ThreadPoolExecutor(
3, // 코어 스레드 개수
100, // 최대 스레드 개수
120L, // 놀고 있는 시간
TimeUnit.SECONDS, // 놀고 있는 시간 단위
new SynchronousQueue<Runnable>() // 작업 큐
);
- 예3)
- ThreadPoolExecutor로 직접 스레드풀을 생성할 수도 있다.
- 코드에서 초기 수 0개 / 코어 수 3개 최대 수 100개인 스레드풀을 생성한다.
- 추가된 스레드가 120초 동안 놀고 있을 경우 해당 스레드를 풀에서 제거한다.
스레드풀 종료
- 스레드풀의 스레드는 기본적으로 데몬 스레드가 아니기 때문에 main 스레드가 종료되더라도 작업을 처리하기 위해 계속 실행 상태로 남아 있다.
- 스레드풀의 모든 스레드를 종료하려면 ExecutorService의 두 메소드 중 하나를 실행해야 한다.
리턴 타입 | 메소드명(매개변수) | 설명 |
void | shutdown() | 현재 처리 중인 작업뿐만 아니라 작업 큐에 대기하고 있는 모든 작업을 처리한 뒤에 스레드풀을 종료시킨다. |
List<Runnable> | shutdownNow() | 현재 작업 처리 중인 스레드를 interrupt해서 작업을 중지시키고 스레드풀을 종료시킨다. 리턴값은 작업 큐에 있는 미처리된 작업(Runnable)의 목록이다. |
작업 생성과 처리 요청
- 하나의 작업은 Runnable 또는 Callable 구현 객체로 표현한다.
- Runnable과 Callable의 차이점: 작업 처리 완료 후 리턴값이 있느냐 없느냐
// Runnable 익명 구현 객체
new Runnable() {
@Override
public void run() {
// 스레드가 처리할 작업 내용
}
}
// Callable 익명 구현 객체
new Callable() {
@Override
public T call() throws Exception {
// 스레드가 처리할 작업 내용
return T;
}
}
Runnable의 run() 메소드는 리턴값이 없고, Callable의 call() 메소드는 리턴값이 있다.
- 작업 처리 요청: ExecutorService의 작업 큐에 Runnable 또는 Callable 객체를 넣는 행위
- 작업 처리 요청을 위해 ExecutorService는 두 가지 메소드를 제공한다.
리턴 타입 | 메소드명(매개변수) | 설명 |
void | execute(Runnable command) | - Runnable을 작업 큐에 저장 - 작업 처리 결과를 리턴하지 않음 |
Future<T> | submit(Callable<T> task) | - Callable을 작업 큐에 저장 - 작업 처리 결과를 얻을 수 있도록 Future를 리턴 |
- Runnable 또는 Callable 객체가 ExevutorService의 작업 큐에 들어가면 ExevutorService는 처리할 스레드가 있는지 보고, 없다면 스레드를 새로 생성시킨다.
- 스레드는 작업 큐에서 Runnable 또는 Callable 객체를 꺼내와 run() 또는 call() 메소드를 실행하면서 작업을 처리한다.

이 글은 『이것이 자바다』 책을 학습한 내용을 정리한 것입니다.
'프로그래밍 > Java' 카테고리의 다른 글
[스터디4] 09. 자바 21에서 강화된 언어 및 라이브러리 (0) | 2025.04.02 |
---|---|
[스터디4] 08. 스트림 요소 처리 (0) | 2025.03.21 |
[스터디4] 06. 제네릭 (0) | 2025.03.02 |
[스터디4] 05. 중첩 선언과 익명 객체 (0) | 2025.02.25 |
[스터디4] 04. 인터페이스 (1) | 2025.02.16 |
Comments