동기(Synchronous)와 비동기(Asynchronous)

동기의 정의와 특징
- 동기(Synchronous) 는 작업을 순차적으로 수행하는 것을 의미
- 이전 작업이 완료되어야 다음 작업을 시작
- 요청한 작업에 대한 완료 여부를 확인하고 나서 다음 작업으로 넘어감
예시
- A 작업이 완료된 후에 B 작업을 시작함
- 파일을 읽을 때, 파일을 다 읽을 때까지 기다림
JAVA 예제
// 동기적으로 파일 읽기
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;
public class SyncExample {
public static void main(String[] args) throws IOException {
System.out.println("Start");
byte[] data = Files.readAllBytes(Paths.get("file.txt"));
System.out.println("File content: " + new String(data));
System.out.println("End");
}
}
Files.readAllBytes 메서드는 파일을 모두 읽을 때까지 대기함
- 파일을 다 읽은 후에 다음 코드가 실행됨
비동기의 정의와 특징
- 비동기(Asynchronous) 는 작업의 완료 여부에 관심 없이 다음 작업을 수행함
- 여러 작업이 동시에 진행될 수 있음
- 작업 완료 시 콜백이나 Future 등을 통해 결과를 처리함
JAVA 예제
// 비동기적으로 파일 읽기
import java.nio.file.*;
import java.util.concurrent.*;
public class AsyncExample {
public static void main(String[] args) {
System.out.println("Start");
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
byte[] data = Files.readAllBytes(Paths.get("file.txt"));
System.out.println("File content: " + new String(data));
} catch (IOException e) {
e.printStackTrace();
}
});
System.out.println("End");
// 메인 스레드가 종료되지 않도록 잠시 대기
// 실제 비동기 작업에서는 메인 스레드가 다른 작업을 수행함
try {
Thread.sleep(2000); // 예시를 위해 잠시 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 파일을 읽는 동안
main 메서드는 다음 코드를 실행함
- 작업이 완료되면 콜백을 통해 결과를 처리함
Thread.sleep은 프로그램이 바로 종료되지 않도록 잠시 대기하는 용도이며, 실제 비동기 프로그그램에서는 메인 스레드가 다른 작업을 수행함
동기와 비동기의 차이점
- 작업 순서의 보장 여부 : 동기는 순서가 보장되지만, 비동기는 순서가 보장되지 않을 수 있음
- 작업 완료 여부 확인 : 동기는 작업이 완료될 때까지 기다리지만, 비동기는 기다리지 않고 다음 작업을 수행함
블로킹(Blocking)과 논블로킹(Non-Blocking)
블로킹의 정의와 특징
- 블로킹(Blocking) 은 현재 진행 중인 작업이 완료될 때까지 다른 작업을 수행하지 못하는 것을 의미
- 함수 호출 시 제어권이 호출된 함수로 넘어가며, 호출자는 작업이 끝날 때까지 기다림

예시
- 네트워크 요청을 보낼 때, 응답이 올 때까지 대기함
- 스레드가 I/O 작업을 수행하는 동안 다른 작업을 하지 못함
JAVA 예제
// 블로킹 방식의 HTTP 요청
import java.io.*;
import java.net.*;
public class BlockingHttpExample {
public static void main(String[] args) throws Exception {
System.out.println("Start");
URL url = new URL("https://example.com");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
// 응답이 올 때까지 대기
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println("Response: " + content.toString());
System.out.println("End");
}
}
- 응답이 올 때까지 대기하기 때문에 다른 작업을 수행할 수 없음
논블로킹의 정의와 특징
- 논블로킹(Non-Blocking) 은 현재 작업이 진행되는 동안에도 다른 작업을 수행할 수 있는 것을 의미
- 함수 호출 시 제어권이 즉시 반환되어 호출자는 다른 작업을 계속 수행함

예시
- 네트워크 요청을 보내고, 응답을 기다리지 않고 다른 작업을 수행함
- 콜백이나
Future를 통해 결과를 처리함
JAVA 예제
// 논블로킹 방식의 HTTP 요청
import java.net.http.*;
import java.net.URI;
import java.util.concurrent.*;
public class NonBlockingHttpExample {
public static void main(String[] args) {
System.out.println("Start");
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com"))
.build();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenAccept(response -> {
System.out.println("Response: " + response.body());
})
.exceptionally(e -> {
e.printStackTrace();
return null;
});
System.out.println("Doing other work...");
// 메인 스레드가 종료되지 않도록 잠시 대기
try {
Thread.sleep(5000); // 예시를 위해 잠시 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("End");
}
}
- 네트워크 요청을 보내고 바로 다음 작업을 수행함
- 응답이 오면 콜백을 통해 결과를 처리함
Thread.sleep은 프로그램이 바로 종료되지 않도록 잠시 대기하는 용도이며, 실제 논블로킹 프로그램에서는 메인 스레드가 다른 작업을 수행함
블로킹과 논블로킹의 차이점
- 제어권의 이동 여부 : 블로킹은 제어권이 호출된 함수로 넘어가고, 논블로킹은 호출자에게 남아 있음
- 다른 작업 수행 가능 여부 : 블로킹은 다른 작업을 수행하지 못하지만, 논블로킹은 다른 작업을 동시에 수행할 수 있음
동기/비동기와 블로킹/논블로킹의 관계
개념의 혼동과 차이점
- 동기/비동기는 작업의 완료 여부와 순서에 관한 개념
- 블로킹/논블로킹은 제어권의 이동과 다른 작업 수행 가능 여부에 관한 개념
- 이 둘은 서로 다른 차원에서 작업의 수행 방식을 설명하지만, 실제 프로그래밍에서는 혼용되어 사용되기도 함
제어권의 관점에서 본 블로킹/논블로킹
- 제어권은 함수나 프로세스의 실행 흐름을 제어할 수 있는 권한
- 블로킹은 호출된 함수가 작업을 완료할 때까지 제어권을 가지고 있음
- 논블로킹은 호출자는 제어권을 유지하며, 호출된 함수의 작업 완료를 기다리지 않음
동기/비동기와 블로킹/논블로킹의 조합
Sync Blocking
- 동기 + 블로킹
- 다른 작업이 완료될 때까지 대기하고, 작업의 완료 여부를 확인하여 순차적으로 처리함

JAVA 예제
// 동기 + 블로킹 방식의 파일 처리
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;
public class SyncBlockingExample {
public static void main(String[] args) throws IOException {
System.out.println("Start");
byte[] data1 = Files.readAllBytes(Paths.get("file1.txt"));
System.out.println("File1 content: " + new String(data1));
byte[] data2 = Files.readAllBytes(Paths.get("file2.txt"));
System.out.println("File2 content: " + new String(data2));
byte[] data3 = Files.readAllBytes(Paths.get("file3.txt"));
System.out.println("File3 content: " + new String(data3));
System.out.println("End");
}
}
- 파일을 하나씩 읽고, 읽기가 완료될 때까지 다음 작업을 수행하지 않음
- 작업이 순차적으로 이루어짐
Async Non-Blocking
- 비동기 + 논블로킹
- 다른 작업을 수행하는 동안에도 제어권을 유지하고, 작업의 완료 여부에 관심 없이 다음 작업을 수행함

JAVA 예제
// 비동기 + 논블로킹 방식의 파일 처리
import java.nio.file.*;
import java.util.concurrent.*;
public class AsyncNonBlockingExample {
public static void main(String[] args) throws Exception {
System.out.println("Start");
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
try {
byte[] data = Files.readAllBytes(Paths.get("file1.txt"));
System.out.println("File1 content: " + new String(data));
} catch (IOException e) {
e.printStackTrace();
}
});
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
try {
byte[] data = Files.readAllBytes(Paths.get("file2.txt"));
System.out.println("File2 content: " + new String(data));
} catch (IOException e) {
e.printStackTrace();
}
});
CompletableFuture<Void> future3 = CompletableFuture.runAsync(() -> {
try {
byte[] data = Files.readAllBytes(Paths.get("file3.txt"));
System.out.println("File3 content: " + new String(data));
} catch (IOException e) {
e.printStackTrace();
}
});
System.out.println("All file read requests initiated.");
System.out.println("End");
// 모든 작업이 완료될 때까지 대기
CompletableFuture.allOf(future1, future2, future3).get();
}
}
- 파일 읽기 요청을 동시에 보내고, 완료 여부와 상관없이 다음 코드를 실행함
- 작업이 완료되면 각자의 콜백에서 결과를 처리함
Sync Non-Blocking
- 동기 + 논블로킹
- 실제로는 비동기 + 블로킹에 가까움
- 제어권을 유지하면서도, 작업의 완료 여부를 확인하여 순차적으로 처리함

JAVA 예제
// 동기 + 논블로킹 방식의 파일 처리
import java.nio.file.*;
import java.util.concurrent.*;
public class SyncNonBlockingExample {
public static void main(String[] args) throws Exception {
System.out.println("Start");
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<byte[]> future = executor.submit(() -> {
return Files.readAllBytes(Paths.get("file.txt"));
});
// 다른 작업 수행 가능
System.out.println("Doing other work...");
// 작업의 완료 여부를 확인하여 순차적으로 처리
byte[] data = future.get(); // 완료될 때까지 대기
System.out.println("File content: " + new String(data));
executor.shutdown();
System.out.println("End");
}
}
- 작업을 백그라운드에서 실행하면서도, 결과를 필요로 할 때까지 다른 작업을 수행함
future.get()을 호출하여 결과를 기다리며, 이때는 블로킹됨
Async Blocking
- 비동기 + 블로킹
- 다른 작업의 완료 여부에 관심 없이 다음 작업을 수행하지만, 제어권을 잃어 다른 작업을 수행하지 못함
특징
- 일반적으로 잘 사용되지 않음
- 작업은 비동기로 시작하지만, 호출자는 제어권을 잃어버림