[Java 동시성] 동기, 비동기와 블로킹, 논블로킹의 개념과 사례
동기/비동기는 작업 순서의 관점에서, 블로킹/논블로킹은 제어권의 관점에서 바라보는 프로그래밍 실행 방식입니다.
Sync/Async & Blocking/Non-blocking
- 동기: 작업 순서가 보장됨
- 비동기: 작업 순서가 보장되지 않음
- 블로킹: 제어권을 넘기지 않음
- 논블로킹: 제어권을 넘김
실제 사례별 분석과 코드
실생활 사례를 생각해보면?
- Sync & Blocking
- 은행 창구 상담번호표의 순서대로 상담(동기), 상담 중인 고객의 업무가 끝날 때까지 다음 고객은 대기(블로킹)
- Async & Non-blocking
- 카페 주문 처리주문 순서와 음료 완성 순서 무관(비동기), 바리스타가 음료 제조 중에도 다른 주문 접수 가능(논블로킹)
- Sync & Non-blocking
- 식당 대기 순서대로 입장(동기), 대기 중 다른 활동 가능(논블로킹)
- Async & Blocking
- 중고거래 상대 선택 순서 자유(비동기), 한 거래 완료 전 다른 거래 불가(블로킹)
코드로 이해해보자
100퍼센트 정확한 예시는 아니지만, 이런 느낌으로 흘러간다는 것을 보여주기 위한 코드이다.
-
Sync & Blocking
// 동기적으로 파일 읽기 FileInputStream fis = new FileInputStream("file.txt"); byte[] data = new byte[100]; fis.read(data); FileInputStream fis2 = new FileInputStream("file2.txt"); byte[] data2 = new byte[100]; fis2.read(data2);
- 파일을 읽는 동안 스레드가 차단되며(Blocking)
- 작업이 순차적으로 실행됨(Sync)
-
Async & Non-blocking
// CompletableFuture: 비동기 지원 CompletableFuture.supplyAsync(() -> { return heavyTask(); }).thenAccept(result -> { System.out.println("처리 완료: " + result); }); System.out.println("done");
- 작업이 비동기적으로 실행(Async)
- 다른 작업 실행 가능(Non-Blocking)
- 즉, “처리 완료” 보다 done”이 먼저 출력 될 수 있다.
- 1번째 예제인 파일 읽기를 이곳에 적용하면?
파일 따로 읽으면서 메인 코드는 계속 다른 일을 수행할 수 있는 것이다.
-
Sync & Non-blocking
// Java NIO Selector 예제 Selector selector = Selector.open(); ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.configureBlocking(false); // non-blocking 모드 설정 serverSocket.register(selector, SelectionKey.OP_ACCEPT); while (true) { selector.select(); // 이벤트가 발생할 때까지 대기 Set<SelectionKey> selectedKeys = selector.selectedKeys(); for (SelectionKey key : selectedKeys) { if (key.isAcceptable()) { // 새로운 연결 처리 } else if (key.isReadable()) { // 데이터 읽기 처리 } } selectedKeys.clear(); }
- Sync: selector.select()를 통해 이벤트를 순차적으로 처리
- Non-Blocking: 채널들이 non-blocking 모드로 설정되어 있어 I/O 작업이 즉시 반환
-
보충 설명
- 위 예제에선 Java NIO로 양방향 TCP 통신하는 실시간 채팅을 구현할 수 있는데,
요청 순서대로 채팅이 올라오고(Sync),
채팅이 올라오면서 언제든지 채팅을 입력할 수도 있다.(Non-blocking) - 번외) 백그라운드에서 돌아가는 요청 작업을 스레드1개, 메인 코드가 수행되는 메인 스레드 1개가 있다고 생각해보자.
while문을 통해 스레드가 모두 처리되었는지 끊임없이 확인하고, 처리가 완료되면 다음 메인 작업을 수행한다. - 번외) JS의 async/await은 비동기 논블로킹 방식을 동기 논블로킹 방식으로 바꿔주는 기법이니까 JS에서 비동기를 우린 동기처럼 사용가능 한 것이었다.
그러나, 내부적으로 비동기 논블로킹 방식으로 동작한다는 점 유의.
- 위 예제에선 Java NIO로 양방향 TCP 통신하는 실시간 채팅을 구현할 수 있는데,
-
Async & Blocking
// Future: 비동기 지원 Future<ResultSet> future = executor.submit(() -> { return statement.executeQuery("SELECT * FROM users"); }); ResultSet result = future.get();
- future.get()이 결과를 기다리며 스레드를 차단(Blocking)
- Java의 Future가 비동기라 해도, MySQL 드라이버가 블로킹 방식이다.
- MySQL의 결과값을 필요로 하다보니 기다리게 되는데, 결국 Sync Blocking 작업 수행과 차이가 없게 된다.
- 이해하기 어렵긴 한데, 어차피 실무에서 볼 일은 거의 없다.
- 작업은 비동기적으로 실행(Async)
- future.get()이 결과를 기다리며 스레드를 차단(Blocking)
각 방식은 상황에 따라 적절히 선택되어야 하며, 시스템의 요구사항과 특성을 고려해야 합니다.
댓글남기기