-
1️⃣ Synchronous(동기) vs Asynchronous(비동기)
-
1) 개념
-
2) 장단점 비교
-
2️⃣ Blocking vs Non-Blocking (블로킹 vs 논블로킹)
-
1) 개념
-
2) Blocking & Non-Blocking I/O
-
3️⃣ Sync / Async + Blocking / Non-Blocking
-
1) Sync + Blocking (동기 + 블로킹)
-
2) Async + Blocking (비동기 + 블로킹)
-
3) Sync + Non-Blocking (동기 + 논블로킹)
-
4) Async + Non-Blocking (비동기 + 논블로킹)
1️⃣ Synchronous(동기) vs Asynchronous(비동기)
1) 개념
우선 '동기'와 'Synchronous'의 어원부터 살펴보자. 한자로는 '同期'로, '같은 시각'이라는 뜻을 가진다. 이를 보면 얼추 이해할 수 있을 것 같지만, 꽤나 헷갈릴 수 있는 개념이기 때문에 영어의 어원도 함께 살펴보자. 우선 그리스어로 'Syn' 은 with, together인 '같이, 함께'의 뜻을 가진다. 다음으로 'chrono'는 '시각'의 뜻을 가진다. 즉, Syn + chrono + us 이 세 단어가 합쳐져 'Synchronous'가 완성된 것이다.
'Asynchronous'는 'Synchronous'에 반대를 나타내는 접두어인 'a'를 붙였다. 다시 말해 동기와 비동기는 반대의 개념이다. 이러한 개념을 바탕으로 동기와 비동기의 차이는 작업 시간을 함께 맞춰 진행하냐, 하지 않냐로 구분할 수 있다. 결국 동기와 비동기의 개념은 작업을 수행하는 주체가 두 개 이상일 때, 수행해야하는 작업들에 대한 흐름을 어떻게 처리해야 하는지에 대한 관점으로 생각하면 된다.
Synchronous (동기)
동기는 같은 시각에 처리되는 것, 특정 작업이 동시에 시작하거나, 동시에 끝나거나, 하나의 작업이 끝나는 동시에 다른 작업이 시작되는 것을 말한다. 이 때 작업 중에 다른 작업은 끼어들지 못한다. 이는 요청 작업에 대해 완료 여부를 따져 순차적으로 처리하는 것을 말한다.
요청 이후 응답을 받아야만 다음 작업이 이루어지며, 특정 작업을 처리할 동안 다른 작업들을 정지한다. 이러한 이유로 요청과 응답의 순서가 보장된다. 아래 그림을 보면 조금 더 이해가 쉽다.

코드를 예로 들면 실행의 흐름은 위에서 아래로 진행되며, 하나의 작업이 시작되면 해당 작업이 완료될 때까지 다음 작업은 대기한다.
함수를 호출한다면 함수의 실행이 끝나기를 기다리고, 그 결과를 받아야 다음 작업을 진행할 수 있는 것이다.
아래는 동기적으로 실행되는 간단한 코드 예제이다.
// Java
public static void main() {
System.out.println("task start");
System.out.println("task proceed");
System.out.println("task end");
}
// Javascript
console.log("task start");
console.log("task proceed");
console.log("task end");
ASynchronous (비동기)
비동기는 동기의 반대이다. 요청 작업에 대해 완료 여부를 따져 순차적으로 처리하지 않고, 작업의 시작과 종료가 일치하지 않는다.
또한 동시에 시작하지 않음을 의미한다. 비동기는 작업들의 요청과 응답의 순서가 보장되지 않으며, 작업의 완료 여부를 신경쓰지 않는다.
비동기적인 코드는 주로 콜백 함수, Promise, async/await 같은 메커니즘을 사용하며, 작업이 완료되면 특정 동작을 수행한다.
이를 통해 여러 작업을 동시에 진행하거나, 작업이 완료되기를 기다리지 않고 다른 작업을 처리할 수 있다.

아래는 비동기적으로 실행되는 간단한 코드 예제이다.
// Java
public class Main {
public static void main(String[] args) {
// ExecutorService를 생성하여 스레드 풀 관리
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 비동기 작업 정의
Runnable otherTask = new OtherTask();
executorService.execute(otherTask);
// MainTask 비동기적 실행
executorService.execute(() -> {
try {
System.out.println("MainTask");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
executorService.shutdown();
}
}
class OtherTask implements Runnable {
@Override
public void run() {
try {
System.out.println("otherTask");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// Javascript
console.log("task start");
// 'setTimeout' 함수는 비동기적으로 동작, 특정 시간 경과 후 콜백 함수 실행
setTimeout(function() {
console.log("task proceed");
}, 1000);
console.log("task end");
2) 장단점 비교
1) Synchronous (동기)
- 장점
- 직관적인 코드 : 동기 코드는 순차적으로 실행되기 때문에 코드의 흐름이 직관적임. 따라서 디버깅이나 코드 이해가 쉽다.
- 동기적 에러 처리 : 에러가 발생하면 발생 지점에서 예외가 즉시 발생하기 때문에, 오류 처리가 상대적으로 간단하다.
- 코드 일관성 : 동기 코드는 한 번에 하나의 작업을 처리하므로 데이터 일관성을 유지하기가 더 쉽다.
- 단순한 구현 : 동기 코드는 비동기 코드에 비해 간단하게 구현할 수 있다.
- 적은 컨텍스트 스위칭 비용 : 스레드 간의 컨텍스트 스위칭 비용이 적게 발생한다.
- 단점
- 성능 제한 : 대규모 작업이나 여러 작업을 동시에 처리할 때 성능 저하 혹은 제한이 발생할 수 있다.
- 대기 시간 발생 : 하나의 작업이 완료될 때까지 다음 작업이 대기하므로 대기 시간이 발생할 수 있다.
- 자원 낭비 가능성 : 대기 시간동안 자원이 유휴 상태일 수 있으므로 효율적인 자원 관리가 어려울 수 있다.
- 느린 응답 시간 : 모든 작업을 순차적으로 실행하기 때문에 응답 시간이 늘어날 수 있다.
- 확장성 제한 : 동기 코드는 한 번에 하나의 작업만 처리하므로 확장성이 제한될 수 있다.
2) Asynchronous (비동기)
- 장점
- 높은 성능 : 여러 작업을 동시에 처리할 수 있으므로 성능이 향상될 수 있다.
- 대기 시간 감소 : 비동기 작업은 한 작업이 완료될 때까지 기다릴 필요가 없어 대기 시간이 감소한다.
- 자원 효율성 증가 : 대기 시간 동안 다른 작업을 수행할 수 있으므로 자원의 효율적인 활용이 가능하다.
- 빠른 응답 시간 : 비동기 작업은 병렬로 실행되므로 전반적인 응답 시간이 단축될 수 있다.
- 확장성 : 비동기 코드는 여러 작업을 처리할 수 있으므로 확장성이 높다.
- 단점
- 코드 복잡성 증가 : 비동기 코드는 콜백이나 Promise 등을 사용해 구현되기 때문에 코드가 복잡해질 수 있다.
- 에러 처리의 어려움 : 동기 코드보다 에러 처리가 어려울 수 있다.
- 콜백 지옥 : 비동기 중첩이 많아지면 콜백 지옥(Callback Hell)이 발생하여 코드 가독성이 떨어질 수 있다.
- 비동기적 로직 이해의 어려움 : 코드의 실행 흐름이 순차적이지 않으므로 복잡한 로직을 이해하기 어려울 수 있다.
- 동기 코드와 호환성의 어려움 : 동기 코드와 비동기 코드를 함께 사용할 때 호환성이 맞지 않아 문제가 발생할 수 있다.
2️⃣ Blocking vs Non-Blocking (블로킹 vs 논블로킹)
1) 개념
블로킹과 논블로킹은 동기/비동기와 혼동될 수 있으며, 명확한 개념을 모른다면 동일하다고 생각할 수도 있다. 하지만 관점을 달리 해야 한다. 앞서 동기/비동기는 '수행해야하는 작업들에 대한 흐름을 어떻게 처리해야 하는지에 대한 관점'이라고 했다. 그렇다면 블로킹과 논블로킹은 '다른 요청의 작업을 처리하기 위해 차단해야 하는지, 대기해야하는지에 대한 관점'으로 생각하면 된다.
Blocking (블로킹)
블로킹은 어떤 작업이 완료될 때까지 제어권이 다음 단계로 넘어가지 않고 멈춰있는 상태를 말한다. 여기서 제어권은 함수나 프로세스의 실행 흐름을 제어할 수 있는 권리를 말한다. 즉, 자신의 작업을 진행하다가 다른 작업이 시작되면 해당 작업이 끝날 때까지 기다렸다가 자신의 작업을 시작하는 방식이다. 예를 들어 사용자 입력을 받는 작업이 있을 때, 블로킹 방식으로 읽으면 입력을 다 읽을 때까지 대기하여 다른 작업은 잠시 중단하는 것이다. 다른 작업의 실행이 현재 작업의 실행을 막는다면 블로킹이다.

Non-Blocking (논블로킹)
논블로킹은 블로킹의 반대 개념으로, 어떤 작업의 완료 여부와 관계 없이 자신의 작업을 할 수 있는 상태를 말한다. 즉, 제어권을 위임하지 않고 자신이 그대로 갖고 있는다. 논블로킹은 다른 작업을 수행하거나, 작업이 완료되면 콜백 함수를 호출하여 처리한다. 만약 함수 A, B가 있을 때 A가 B를 호출하면 B가 실행되지만 제어권은 여전히 A가 갖고 있다. 따라서 B를 호출하더라도 A는 계속 실행된다.

2) Blocking & Non-Blocking I/O
Blocking I/O
블로킹 I/O는 데이터를 읽거나 쓸 때, 해당 작업이 완료될 때까지 프로그램이나 스레드가 대기하는 방식이다. 데이터가 도착하거나 송신될 떄까지 입출력 작업이 블로킹되어 다음 코드나 작업으로 진행되지 않는다. 이는 입출력 작업이 완료될 때까지 해당 스레드가 아무런 작업을 수행하지 않는 상태를 의미한다.
InputStream inputStream = new FileInputStream("example.txt");
int data = inputStream.read(); // 데이터를 읽을 때까지 블로킹

- 유저가 커널에 'read' 작업 요청
- 데이터가 입력될 때까지 유저는 대기
- 데이터가 입력되면 커널은 유저에게 결과를 전달
Non-Blocking I/O
논블로킹 I/O는 입출력 작업이 완료되지 않았더라도 대기하지 않고 다른 작업을 수행할 수 있는 방식이다. 입출력 작업을 시작하고 해당 작업이 완료될 때까지 기다리지 않고 다음 코드나 작업을 수행한다. 논블로킹 I/O는 입출력 작업이 완료되었을 때 이벤트나 콜백을 통해 알림을 받아 처리하는 방식으로 동작한다.
AsynchronousFileChannel channel = AsynchronousFileChannel.open(Paths.get("example.txt"));
Future<Integer> result = channel.read(buffer, 0); // 데이터 읽기 작업이 완료되지 않아도 다른 작업을 수행할 수 있음

- 유저가 커널에 'read' 작업 요청
- 데이터 입력 여부와 상관 없이 요청 순간 결과를 바로 반환
- 입력 데이터가 없다면 1, 2번 반복
- 입력 데이터가 있다면 커널은 유저에게 결과를 전달
3️⃣ Sync / Async + Blocking / Non-Blocking
동기 / 비동기와 블로킹 / 논블로킹을 공부하면서 비슷한 개념인 것 같다는 느낌을 많이 받았다. 혼용되어 사용되긴 하지만 이는 엄연히 다른 개념이므로 잘 알아두자. 따라서 실제 프로그램에서는 해당 개념들을 조합하여 사용한다. 아래 관점을 잘 생각해보자.
1. Sync / Async : 처리해야 할 작업들을 어떤 '흐름'으로 처리 할 것인가, 리턴 값 확인
- Sync : 리턴 값을 기다림. 작업이 동시에 시작 또는 종료하거나, 끝나는 동시에 시작
- Async : 리턴 값을 기다리지 않고 독립적으로 동작. 작업의 시작과 종료가 일치하지 않는다.
2. Blocking / Non-Blocking : 처리해야 할 하나의 작업이 전체 작업의 '흐름'을 막을 것인가, 제어권 확인
- Blocking : 제어권을 넘기고 이를 다시 돌려받을 때까지 대기했다가 제어권을 받으면 동작 수행
- Non-Blocking : 제어권을 넘기지 않음

실행활에서 은행 업무를 보는 상황을 예시로 설명하겠다. 은행원과 고객이 있다고 가정하자.
1) Sync + Blocking (동기 + 블로킹)
- 다른 작업이 진행되는 동안 자신의 작업을 처리하지 않는다. (Blocking)
- 다른 작업이 완료되면 순차적으로 다음 작업을 처리한다. (Sync)
- 고객 1 : 통장 만들어주세요.
- 은행원 : 알겠습니다. 기다려주세요.
- 고객 1 : (은행원이 통장을 만들때까지 앞에 앉아 아무것도 하지 않고 기다림)
- 은행원 : 통장 다 만들었습니다.
2) Async + Blocking (비동기 + 블로킹)
- 다른 작업이 진행되는 동안 자신의 작업을 처리하지 않는다. (Blocking)
- 다른 작업의 결과를 바로 처리하지 않고, 순차적으로 처리하지 않는다. (Async)
해당 방식은 실무에서 흔히 사용되는 방식은 아니라고 한다. 따라서 예시는 생략.
3) Sync + Non-Blocking (동기 + 논블로킹)
- 다른 작업이 진행되는 동안 자신의 작업을 처리한다. (Non-Blocking)
- 다른 작업이 완료되면 순차적으로 다음 작업을 처리한다. (Sync)
- 고객 1 : (은행원 1에게) 통장 만들어주세요.
- 은행원 1 : 알겠습니다. 기다려주세요.
- 고객 1 : 대출을 하려면 통장을 만들어야하는데 다 만들었나요?
- 은행원 1 : 아직 만들고 있습니다. 보채지 마세요.
- 고객 1 : 통장 다 만들었나요?
- 은행원 1 : 아니요아니요아니요.
- 은행원 1 : 통장 다 만들었습니다.
- 고객 1 : 통장 받았습니다.
- 고객 1 : (은행원 2에게) 대출해주세요.
- 은행원 2 : 알겠습니다. 기다려주세요.
4) Async + Non-Blocking (비동기 + 논블로킹)
- 다른 작업이 진행되는 동안 자신의 작업을 처리한다. (Non-Blocking)
- 다른 작업의 결과를 바로 처리하지 않고, 순차적으로 처리하지 않는다. (Async)
- 고객 1 : (은행원 1에게) 통장 만들어주세요. (동시)
- 고객 1 : (은행원 2에게) 카드 만들어주세요. (동시)
- 고객 1 : (은행원 3에게) 노래 추천해주세요. (동시)
- 고객 1 : 유튜브 보고 있어야겠다.
- 은행원 3 : 당근송이요.
- 은행원 1 : 통장 만들었습니다.
- 은행원 2 : 카드 만들었습니다.
참고
1️⃣ Synchronous(동기) vs Asynchronous(비동기)
1) 개념
우선 '동기'와 'Synchronous'의 어원부터 살펴보자. 한자로는 '同期'로, '같은 시각'이라는 뜻을 가진다. 이를 보면 얼추 이해할 수 있을 것 같지만, 꽤나 헷갈릴 수 있는 개념이기 때문에 영어의 어원도 함께 살펴보자. 우선 그리스어로 'Syn' 은 with, together인 '같이, 함께'의 뜻을 가진다. 다음으로 'chrono'는 '시각'의 뜻을 가진다. 즉, Syn + chrono + us 이 세 단어가 합쳐져 'Synchronous'가 완성된 것이다.
'Asynchronous'는 'Synchronous'에 반대를 나타내는 접두어인 'a'를 붙였다. 다시 말해 동기와 비동기는 반대의 개념이다. 이러한 개념을 바탕으로 동기와 비동기의 차이는 작업 시간을 함께 맞춰 진행하냐, 하지 않냐로 구분할 수 있다. 결국 동기와 비동기의 개념은 작업을 수행하는 주체가 두 개 이상일 때, 수행해야하는 작업들에 대한 흐름을 어떻게 처리해야 하는지에 대한 관점으로 생각하면 된다.
Synchronous (동기)
동기는 같은 시각에 처리되는 것, 특정 작업이 동시에 시작하거나, 동시에 끝나거나, 하나의 작업이 끝나는 동시에 다른 작업이 시작되는 것을 말한다. 이 때 작업 중에 다른 작업은 끼어들지 못한다. 이는 요청 작업에 대해 완료 여부를 따져 순차적으로 처리하는 것을 말한다.
요청 이후 응답을 받아야만 다음 작업이 이루어지며, 특정 작업을 처리할 동안 다른 작업들을 정지한다. 이러한 이유로 요청과 응답의 순서가 보장된다. 아래 그림을 보면 조금 더 이해가 쉽다.

코드를 예로 들면 실행의 흐름은 위에서 아래로 진행되며, 하나의 작업이 시작되면 해당 작업이 완료될 때까지 다음 작업은 대기한다.
함수를 호출한다면 함수의 실행이 끝나기를 기다리고, 그 결과를 받아야 다음 작업을 진행할 수 있는 것이다.
아래는 동기적으로 실행되는 간단한 코드 예제이다.
// Java
public static void main() {
System.out.println("task start");
System.out.println("task proceed");
System.out.println("task end");
}
// Javascript
console.log("task start");
console.log("task proceed");
console.log("task end");
ASynchronous (비동기)
비동기는 동기의 반대이다. 요청 작업에 대해 완료 여부를 따져 순차적으로 처리하지 않고, 작업의 시작과 종료가 일치하지 않는다.
또한 동시에 시작하지 않음을 의미한다. 비동기는 작업들의 요청과 응답의 순서가 보장되지 않으며, 작업의 완료 여부를 신경쓰지 않는다.
비동기적인 코드는 주로 콜백 함수, Promise, async/await 같은 메커니즘을 사용하며, 작업이 완료되면 특정 동작을 수행한다.
이를 통해 여러 작업을 동시에 진행하거나, 작업이 완료되기를 기다리지 않고 다른 작업을 처리할 수 있다.

아래는 비동기적으로 실행되는 간단한 코드 예제이다.
// Java
public class Main {
public static void main(String[] args) {
// ExecutorService를 생성하여 스레드 풀 관리
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 비동기 작업 정의
Runnable otherTask = new OtherTask();
executorService.execute(otherTask);
// MainTask 비동기적 실행
executorService.execute(() -> {
try {
System.out.println("MainTask");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
executorService.shutdown();
}
}
class OtherTask implements Runnable {
@Override
public void run() {
try {
System.out.println("otherTask");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// Javascript
console.log("task start");
// 'setTimeout' 함수는 비동기적으로 동작, 특정 시간 경과 후 콜백 함수 실행
setTimeout(function() {
console.log("task proceed");
}, 1000);
console.log("task end");
2) 장단점 비교
1) Synchronous (동기)
- 장점
- 직관적인 코드 : 동기 코드는 순차적으로 실행되기 때문에 코드의 흐름이 직관적임. 따라서 디버깅이나 코드 이해가 쉽다.
- 동기적 에러 처리 : 에러가 발생하면 발생 지점에서 예외가 즉시 발생하기 때문에, 오류 처리가 상대적으로 간단하다.
- 코드 일관성 : 동기 코드는 한 번에 하나의 작업을 처리하므로 데이터 일관성을 유지하기가 더 쉽다.
- 단순한 구현 : 동기 코드는 비동기 코드에 비해 간단하게 구현할 수 있다.
- 적은 컨텍스트 스위칭 비용 : 스레드 간의 컨텍스트 스위칭 비용이 적게 발생한다.
- 단점
- 성능 제한 : 대규모 작업이나 여러 작업을 동시에 처리할 때 성능 저하 혹은 제한이 발생할 수 있다.
- 대기 시간 발생 : 하나의 작업이 완료될 때까지 다음 작업이 대기하므로 대기 시간이 발생할 수 있다.
- 자원 낭비 가능성 : 대기 시간동안 자원이 유휴 상태일 수 있으므로 효율적인 자원 관리가 어려울 수 있다.
- 느린 응답 시간 : 모든 작업을 순차적으로 실행하기 때문에 응답 시간이 늘어날 수 있다.
- 확장성 제한 : 동기 코드는 한 번에 하나의 작업만 처리하므로 확장성이 제한될 수 있다.
2) Asynchronous (비동기)
- 장점
- 높은 성능 : 여러 작업을 동시에 처리할 수 있으므로 성능이 향상될 수 있다.
- 대기 시간 감소 : 비동기 작업은 한 작업이 완료될 때까지 기다릴 필요가 없어 대기 시간이 감소한다.
- 자원 효율성 증가 : 대기 시간 동안 다른 작업을 수행할 수 있으므로 자원의 효율적인 활용이 가능하다.
- 빠른 응답 시간 : 비동기 작업은 병렬로 실행되므로 전반적인 응답 시간이 단축될 수 있다.
- 확장성 : 비동기 코드는 여러 작업을 처리할 수 있으므로 확장성이 높다.
- 단점
- 코드 복잡성 증가 : 비동기 코드는 콜백이나 Promise 등을 사용해 구현되기 때문에 코드가 복잡해질 수 있다.
- 에러 처리의 어려움 : 동기 코드보다 에러 처리가 어려울 수 있다.
- 콜백 지옥 : 비동기 중첩이 많아지면 콜백 지옥(Callback Hell)이 발생하여 코드 가독성이 떨어질 수 있다.
- 비동기적 로직 이해의 어려움 : 코드의 실행 흐름이 순차적이지 않으므로 복잡한 로직을 이해하기 어려울 수 있다.
- 동기 코드와 호환성의 어려움 : 동기 코드와 비동기 코드를 함께 사용할 때 호환성이 맞지 않아 문제가 발생할 수 있다.
2️⃣ Blocking vs Non-Blocking (블로킹 vs 논블로킹)
1) 개념
블로킹과 논블로킹은 동기/비동기와 혼동될 수 있으며, 명확한 개념을 모른다면 동일하다고 생각할 수도 있다. 하지만 관점을 달리 해야 한다. 앞서 동기/비동기는 '수행해야하는 작업들에 대한 흐름을 어떻게 처리해야 하는지에 대한 관점'이라고 했다. 그렇다면 블로킹과 논블로킹은 '다른 요청의 작업을 처리하기 위해 차단해야 하는지, 대기해야하는지에 대한 관점'으로 생각하면 된다.
Blocking (블로킹)
블로킹은 어떤 작업이 완료될 때까지 제어권이 다음 단계로 넘어가지 않고 멈춰있는 상태를 말한다. 여기서 제어권은 함수나 프로세스의 실행 흐름을 제어할 수 있는 권리를 말한다. 즉, 자신의 작업을 진행하다가 다른 작업이 시작되면 해당 작업이 끝날 때까지 기다렸다가 자신의 작업을 시작하는 방식이다. 예를 들어 사용자 입력을 받는 작업이 있을 때, 블로킹 방식으로 읽으면 입력을 다 읽을 때까지 대기하여 다른 작업은 잠시 중단하는 것이다. 다른 작업의 실행이 현재 작업의 실행을 막는다면 블로킹이다.

Non-Blocking (논블로킹)
논블로킹은 블로킹의 반대 개념으로, 어떤 작업의 완료 여부와 관계 없이 자신의 작업을 할 수 있는 상태를 말한다. 즉, 제어권을 위임하지 않고 자신이 그대로 갖고 있는다. 논블로킹은 다른 작업을 수행하거나, 작업이 완료되면 콜백 함수를 호출하여 처리한다. 만약 함수 A, B가 있을 때 A가 B를 호출하면 B가 실행되지만 제어권은 여전히 A가 갖고 있다. 따라서 B를 호출하더라도 A는 계속 실행된다.

2) Blocking & Non-Blocking I/O
Blocking I/O
블로킹 I/O는 데이터를 읽거나 쓸 때, 해당 작업이 완료될 때까지 프로그램이나 스레드가 대기하는 방식이다. 데이터가 도착하거나 송신될 떄까지 입출력 작업이 블로킹되어 다음 코드나 작업으로 진행되지 않는다. 이는 입출력 작업이 완료될 때까지 해당 스레드가 아무런 작업을 수행하지 않는 상태를 의미한다.
InputStream inputStream = new FileInputStream("example.txt");
int data = inputStream.read(); // 데이터를 읽을 때까지 블로킹

- 유저가 커널에 'read' 작업 요청
- 데이터가 입력될 때까지 유저는 대기
- 데이터가 입력되면 커널은 유저에게 결과를 전달
Non-Blocking I/O
논블로킹 I/O는 입출력 작업이 완료되지 않았더라도 대기하지 않고 다른 작업을 수행할 수 있는 방식이다. 입출력 작업을 시작하고 해당 작업이 완료될 때까지 기다리지 않고 다음 코드나 작업을 수행한다. 논블로킹 I/O는 입출력 작업이 완료되었을 때 이벤트나 콜백을 통해 알림을 받아 처리하는 방식으로 동작한다.
AsynchronousFileChannel channel = AsynchronousFileChannel.open(Paths.get("example.txt"));
Future<Integer> result = channel.read(buffer, 0); // 데이터 읽기 작업이 완료되지 않아도 다른 작업을 수행할 수 있음

- 유저가 커널에 'read' 작업 요청
- 데이터 입력 여부와 상관 없이 요청 순간 결과를 바로 반환
- 입력 데이터가 없다면 1, 2번 반복
- 입력 데이터가 있다면 커널은 유저에게 결과를 전달
3️⃣ Sync / Async + Blocking / Non-Blocking
동기 / 비동기와 블로킹 / 논블로킹을 공부하면서 비슷한 개념인 것 같다는 느낌을 많이 받았다. 혼용되어 사용되긴 하지만 이는 엄연히 다른 개념이므로 잘 알아두자. 따라서 실제 프로그램에서는 해당 개념들을 조합하여 사용한다. 아래 관점을 잘 생각해보자.
1. Sync / Async : 처리해야 할 작업들을 어떤 '흐름'으로 처리 할 것인가, 리턴 값 확인
- Sync : 리턴 값을 기다림. 작업이 동시에 시작 또는 종료하거나, 끝나는 동시에 시작
- Async : 리턴 값을 기다리지 않고 독립적으로 동작. 작업의 시작과 종료가 일치하지 않는다.
2. Blocking / Non-Blocking : 처리해야 할 하나의 작업이 전체 작업의 '흐름'을 막을 것인가, 제어권 확인
- Blocking : 제어권을 넘기고 이를 다시 돌려받을 때까지 대기했다가 제어권을 받으면 동작 수행
- Non-Blocking : 제어권을 넘기지 않음

실행활에서 은행 업무를 보는 상황을 예시로 설명하겠다. 은행원과 고객이 있다고 가정하자.
1) Sync + Blocking (동기 + 블로킹)
- 다른 작업이 진행되는 동안 자신의 작업을 처리하지 않는다. (Blocking)
- 다른 작업이 완료되면 순차적으로 다음 작업을 처리한다. (Sync)
- 고객 1 : 통장 만들어주세요.
- 은행원 : 알겠습니다. 기다려주세요.
- 고객 1 : (은행원이 통장을 만들때까지 앞에 앉아 아무것도 하지 않고 기다림)
- 은행원 : 통장 다 만들었습니다.
2) Async + Blocking (비동기 + 블로킹)
- 다른 작업이 진행되는 동안 자신의 작업을 처리하지 않는다. (Blocking)
- 다른 작업의 결과를 바로 처리하지 않고, 순차적으로 처리하지 않는다. (Async)
해당 방식은 실무에서 흔히 사용되는 방식은 아니라고 한다. 따라서 예시는 생략.
3) Sync + Non-Blocking (동기 + 논블로킹)
- 다른 작업이 진행되는 동안 자신의 작업을 처리한다. (Non-Blocking)
- 다른 작업이 완료되면 순차적으로 다음 작업을 처리한다. (Sync)
- 고객 1 : (은행원 1에게) 통장 만들어주세요.
- 은행원 1 : 알겠습니다. 기다려주세요.
- 고객 1 : 대출을 하려면 통장을 만들어야하는데 다 만들었나요?
- 은행원 1 : 아직 만들고 있습니다. 보채지 마세요.
- 고객 1 : 통장 다 만들었나요?
- 은행원 1 : 아니요아니요아니요.
- 은행원 1 : 통장 다 만들었습니다.
- 고객 1 : 통장 받았습니다.
- 고객 1 : (은행원 2에게) 대출해주세요.
- 은행원 2 : 알겠습니다. 기다려주세요.
4) Async + Non-Blocking (비동기 + 논블로킹)
- 다른 작업이 진행되는 동안 자신의 작업을 처리한다. (Non-Blocking)
- 다른 작업의 결과를 바로 처리하지 않고, 순차적으로 처리하지 않는다. (Async)
- 고객 1 : (은행원 1에게) 통장 만들어주세요. (동시)
- 고객 1 : (은행원 2에게) 카드 만들어주세요. (동시)
- 고객 1 : (은행원 3에게) 노래 추천해주세요. (동시)
- 고객 1 : 유튜브 보고 있어야겠다.
- 은행원 3 : 당근송이요.
- 은행원 1 : 통장 만들었습니다.
- 은행원 2 : 카드 만들었습니다.
참고