인터럽트 - 시작
ThreadStopMainV2.java
package thread.control.interrupt;
import static util.MyLogger.log;
import static util.ThreadUtils.sleep;
public class ThreadStopMainV2 {
public static void main(String[] args) {
MyTask task = new MyTask();
Thread thread = new Thread(task, "work");
thread.start();
sleep(4000);
log("작업 중단 지시 thread.intterupt()");
thread.interrupt();
log("work 스레드 인터럽트 상태1 = " + thread.isInterrupted());
}
static class MyTask implements Runnable {
@Override
public void run() {
try {
while(true) {
log("작업중");
Thread.sleep(3000);
}
} catch(InterruptedException e) {
log("work 스레드 인터럽트 상태2 = " + Thread.currentThread().isInterrupted());
log("interrupt message = " + e.getMessage());
log("state = " + Thread.currentThread().getState());
}
log("자원 정리");
log("작업 완료");
}
}
}-> 인터럽트가 발생하면 해당 스레드에 InterruptedException 발생
-> 인터럽트를 받은 스레드는 대기 상태에서 깨어나 RUNNABLE이 되고, 코드를 정상 수행한다.
-> intterupt()를 호출했다고 해서 즉각 InterruptedException이 발생하는 것은 아님, sleep()처럼 InterruptedException을 던지는 메서드를 호출하거나 호출 중일 때 예외가 발생함
실행 결과

ThreadStopMainV4.java
package thread.control.interrupt;
import static util.MyLogger.log;
import static util.ThreadUtils.sleep;
public class ThreadStopMainV4 {
public static void main(String[] args) {
MyTask task = new MyTask();
Thread thread = new Thread(task, "work");
thread.start();
sleep(100);
log("작업 중단 지시 thread.intterupt()");
thread.interrupt();
log("work 스레드 인터럽트 상태1 = " + thread.isInterrupted());
}
static class MyTask implements Runnable {
@Override
public void run() {
while(!Thread.interrupted()) {
log("작업중");
}
log("work 스레드 인터럽트 상태2 = " + Thread.currentThread().isInterrupted());
log("자원 정리");
log("작업 완료");
}
}
}
실행 결과

Thread.isinterrupted()
> 단순히 상태 확인
Thread.interrupted()
> 스레드가 인터럽트 상태라면 true 반환, 해당 스레드의 인터럽트 상태를 false로 변경
> 스레드가 인터럽트 상태가 아니라면 false 반환, 해당 스레드의 인터럽트 상태 변경 X
인터럽트 사용 : 프린터 예제
사용자의 입력을 프린터에 출력하는 예제
사용자의 입력을 받는 main 스레드 + 사용자의 입력을 출력하는 printer 스레드
MyPrinterV1.java
package thread.control.printer;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
import java.util.concurrent.ConcurrentLinkedDeque;
import static util.MyLogger.log;
import static util.ThreadUtils.sleep;
public class MyPrinterV1 {
public static void main(String[] args) {
Printer printer = new Printer();
Thread printerThread = new Thread(printer, "printer");
printerThread.start();
Scanner userInput = new Scanner(System.in);
while(true) {
log("프린터할 문서를 입력하세요, 종료 (q): ");
String input = userInput.nextLine();
if(input.equals("q")) {
printer.work = false;
break;
}
printer.addJob(input);
}
}
static class Printer implements Runnable {
volatile boolean work = true;
Queue<String> jobQueue = new ConcurrentLinkedDeque<>();
@Override
public void run() {
while(work) {
if(jobQueue.isEmpty()) {
continue;
}
String job = jobQueue.poll();
log("출력 시작 :" + job + ", 대기 문서: " + jobQueue);
sleep(3000);
log("출력 완료");
}
log("프린터 종료");
}
public void addJob(String input) {
jobQueue.add(input);
}
}
}-> volatitle : 여러 스레드가 동시에 접근하는 변수에는 volatitle 키워드 필요.. (뒤에서 설명)
-> ConcurrentLinkedQueue : 여러 스레드가 동시에 접근하는 경우, 컬렉션 프레임워크의 일반적 자료구조는 안전하지 않아 동시성을 지원하는 동시성 컬렉션을 사용해야 함..(뒤에서 설명)
실행 결과

프린터 작동 그림

- main 스레드 : 사용자 입력을 받아 Printer 인스턴스의 jobQueue에 담음
- printer 스레드 : jobQueue 내용이 있다면 poll()로 출력, 출력하는데 3초의 시간이 걸린다고 가정
프린터 종료 그림

- main 스레드 : 사용자가 q 입력 시, printer.work 값을 false로 변경-> main 스레드는 while문을 빠져나가고 종료된다.
- printer 스레드 : while문에서 work가 false인 것을 확인한다. -> printer 스레드는 while문을 빠져나가고 "프린터 종료" 출력 후 종료된다.
문제 상황
종료 문자 q 입력 시, 바로 반응하지 않음
-> printer 스레드가 반복문을 나오려면 while문을 체크해야 하는데 sleep(3000)을 통해 대기 상태에 빠져 작동하지 않기 때문
-> q 일벽 후 3초 이후에 프린터가 종료되고 있음
인터럽트 도입!
MyPrinterV2.java
package thread.control.printer;
import java.util.Queue;
import java.util.Scanner;
import java.util.concurrent.ConcurrentLinkedDeque;
import static util.MyLogger.log;
import static util.ThreadUtils.sleep;
public class MyPrinterV2 {
public static void main(String[] args) {
Printer printer = new Printer();
Thread printerThread = new Thread(printer, "printer");
printerThread.start();
Scanner userInput = new Scanner(System.in);
while(true) {
log("프린터할 문서를 입력하세요, 종료 (q): ");
String input = userInput.nextLine();
if(input.equals("q")) {
printerThread.interrupt();
break;
}
printer.addJob(input);
}
}
static class Printer implements Runnable {
Queue<String> jobQueue = new ConcurrentLinkedDeque<>();
@Override
public void run() {
while (!Thread.interrupted()) {
if (jobQueue.isEmpty()) {
continue;
}
try {
String job = jobQueue.poll();
log("출력 시작 :" + job + ", 대기 문서: " + jobQueue);
Thread.sleep(3000);
log("출력 완료");
} catch (InterruptedException e) {
log("인터럽트!");
break;
}
log("프린터 종료");
}
}
public void addJob(String input) {
jobQueue.add(input);
}
}
}-> work 변수 제거
-> Thread.interrupted()로 해당 스레드가 인터럽트 상태인지 아닌지 확인
실행 결과

yeild : 양보하기
YeildMain.java
package thread.control.yeild;
import static util.ThreadUtils.sleep;
public class YeildMain {
static final int THREAD_COUNT = 1000;
public static void main(String[] args) {
for(int i=0; i<THREAD_COUNT; i++) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
static class MyRunnable implements Runnable {
@Override
public void run() {
for(int i=0; i<10; i++) {
System.out.println(Thread.currentThread().getName() + " - " + i);
// 1. empty
// sleep(1); // 2. sleep
Thread.yield(); // 3. yield
}
}
}
}
1. empty 실행 결과

-> 특정 스레드가 쭉 수행된 다음 다은 스레드가 수행됨
2. sleep(1) 실행 결과

-> 거의 다른 스레드가 실행됨
-> sleep(1)으로 스레드를 1밀리초 동안 RUNNABLE -> TIMED_WAITING으로 변경하게 되면 스레드는 실행 스케줄링에서 잠시 제외됨
-> TIME_WAITING이 되며 다른 스레드에 실행을 양보함
-> 하지만 양보할 스레드가 없다면 차라리 내 스레드를 실행하는 것이 더 나을지도 모름(양보할 사람이 없는데 굳이 양보?)
3. yeild() 실행 결과

-> 한 스레드가 조금 연달아 수행되다가 바뀌는 것 확인
스레드가 RUNNABLE일 때, 운영체제 스케줄링은 다음과 같은 상태를 가질 수 있다.
1. 실행 상태(Running) : 스레드가 CPU에서 실제 실행 중
2. 실행 대기 상태(Ready) : 스레드가 실행할 준비가 되었지만 CPU가 바빠 스케줄링 큐에서 대기 중
-> OS는 실행 상태의 스레드들을 잠시 실행하고 실행 대기 상태로 만듬, 실행 대기 상태의 스레드들을 잠시만 실행 상태로 변경해 실행함
-> 자바에서 이 둘을 구분할 수는 없음
yield() 작동
1. Thread.yield() : 현재 스레드가 자발적으로 CPU를 양보해 다른 스레드가 실행될 수 있도록
2. yield()를 호출한 스레드는 RUNNABLE을 유지하며 CPU를 양보함, 즉 다시 스케줄링 큐에 들어가며 다른 스레드에게 CPU 사용 기회를 넘김
-> 자바에서 yeild를 호출하면 현재 실행 중인 스레드가 CPU를 양보하도록 힌트를 줌
-> 운영체제 스케줄러에게 힌트를 제공할 뿐, 강제적 실행 순서를 지정하지는 않음
-> RUNNABLE을 유지하기에 양보할 사람이 없다면 본인 스레드가 계속 실행될 수 있음
프린터 예제에 yield 도입!

-> 기존에는 인터럽트 발생 전까지 계속 인터럽트 상태를 체크하고 jobQueue가 비었는지 확인해 CPU 자원을 많이 사용하고 있었음
-> jobQueue에 작업이 비었다면, yield를 호출해 다른 스레드에 작업 양보하도록 개선
'인프런 > 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성' 카테고리의 다른 글
| [인프런] 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성 / 6. 동기화 - synchronized (0) | 2025.03.13 |
|---|---|
| [인프런] 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성 / 5. 메모리 가시성 (0) | 2025.02.26 |
| [인프런] 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성 / 3. 스레드 제어와 생명 주기 1 (1) | 2025.02.02 |
| [인프런] 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성 / 2. 스레드 생성과 실행 (0) | 2025.01.15 |
| [인프런] 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성 / 1. 멀티태스킹과 멀티프로세싱 (1) | 2025.01.06 |